Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Современный CI/CD пайплайн для python микросервисов

Современный CI/CD пайплайн для python микросервисов

Я работаю в команде, где довольно много разработчиков и инженеров. Мы разрабатываем почти в 30 репозиториях, поэтому командный набор CI/CD пайплайнов имеет множество стадий и шагов, а также глубоко централизован.

В своем докладе я постараюсь рассказать историю о том, как он разрабатывался, как эволюционировал и как он выглядит сейчас. Я расскажу, почему мы выбираем централизацию, как и зачем мы используем докер внутри докера, как мы собираем контейнеры без докера, как он помогает нам всегда жить на самом свежем python. Покажу основной набор линтеров для всего на свете, как мы в сотню строчек кода сделали автоматическое версионирование без заморочек. Как чистить за собой реестры пакетов и контейнеров (это не так тривиально, как кажется — покажу почему), как защититься от сбоев пайплайна (ну или попытаться), как разделять среды и переменные (и почему это непросто) и опишу некоторые другие особенности нашего конвейера, раскрывая почти все его детали.

Доклад будет полезен тем, кто только стартует разработку своего конвейера или масштабируется от одного-двух проектов и хочет видеть практически полный список необходимых шагов в CI/CD пайплайне, а также тем, кто никогда ещё не делал конвейеры и не автоматизировал процесс разработки, но хочет или хотя бы интересуется.

https://www.youtube.com/watch?v=7--05FF8VAk
https://pycon.ru/sovremennyj-ci-cd-pajplajn-dlya-python-mikroservisov

Denis Anikin

May 03, 2023
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. Денис Аникин
    https://xfenix.ru/
    Современный CI/CD
    пайплайн для python
    микросервисов

    View full-size slide

  2. Денис Аникин
    2
    Кто я такой:
    — работаю в Райффайзен банке
    https://xfenix.ru

    View full-size slide

  3. Денис Аникин
    3
    Кто я такой:
    — работаю в Райффайзен банке
    — team lead в команде Chat
    https://xfenix.ru

    View full-size slide

  4. Денис Аникин
    4
    Кто я такой:
    — работаю в Райффайзен банке
    — team lead в команде Chat
    — community lead в Python Community
    https://xfenix.ru

    View full-size slide

  5. Денис Аникин
    5
    Кто я такой:
    — работаю в Райффайзен банке
    — team lead в команде Chat
    — community lead в Python Community
    — fullstack: разрабатываю на back на python и front
    на typescript, занимаюсь devops
    https://xfenix.ru

    View full-size slide

  6. Цели рассказа
    6
    Важно для тех, кто никогда их
    ещё не писал
    Помочь начать
    писать пайплайны
    Расскажу коротко какие цели преследовал

    View full-size slide

  7. Цели рассказа
    7
    Важно для тех, кто никогда их
    ещё не писал
    Помочь начать
    писать пайплайны
    Для тех, кто уже пишет давно,
    я принес свой субъективный
    взгляд на вещи
    Натолкнуть на
    мысли
    Расскажу коротко какие цели преследовал

    View full-size slide

  8. Цели рассказа
    8
    Важно для тех, кто никогда их
    ещё не писал
    Помочь начать
    писать пайплайны
    Для тех, кто уже пишет давно,
    я принес свой субъективный
    взгляд на вещи
    Натолкнуть на
    мысли
    Всем остальным может
    пригодиться мой «суповой
    набор» на гитхабе к этой
    презентации
    Поделиться
    наработками
    Расскажу коротко какие цели преследовал

    View full-size slide

  9. Термины
    11

    View full-size slide

  10. Термины
    12
    DevOps: не человек 🤵 🤵‍‍ 🔫, а набор практик
    Нудная часть

    View full-size slide

  11. Термины
    14
    CI: continuous integration. Это когда все часто отправляют свои
    правки в главную ветку
    Нудная часть

    View full-size slide

  12. Термины
    15
    CI: continuous integration. Это когда все часто отправляют свои
    правки в главную ветку
    Мы все в основном играем в continuous isolation ❌
    Нудная часть

    View full-size slide

  13. Термины
    16
    CD:
    — CDL: Continuous DeLivery
    — CDP: Continuous DePloyment
    Это когда у вас (полу)автоматически доставляются релизы
    Нудная часть

    View full-size slide

  14. Термины
    17
    TBD: ?
    Нудная часть

    View full-size slide

  15. Термины
    18
    TBD: trunk based development
    Нудная часть

    View full-size slide

  16. Термины
    19
    TBD: trunk based development
    А теперь моя своеобразная схема! Я рисовал как мог.
    Нудная часть

    View full-size slide

  17. TBD
    20
    Мой взгляд
    Trunk (master или main)

    View full-size slide

  18. TBD
    21
    Мой взгляд
    Trunk (master или main)
    Feature/
    Bugfix/
    Hotfix/

    View full-size slide

  19. TBD
    22
    Мой взгляд
    Trunk (master или main)
    Feature/
    Bugfix/
    Hotfix/
    v1.2.3

    View full-size slide

  20. TBD
    23
    Мой взгляд
    Trunk (master или main)
    Feature/
    Bugfix/
    Hotfix/
    v1.2.3
    v1.2.4

    View full-size slide

  21. Термины
    24
    Монорепозиторий
    Нудная часть

    View full-size slide

  22. Термины
    25
    Мульти репозиторий
    Нудная часть

    View full-size slide

  23. Из чего состоит
    приемлимый
    пайплайн?
    26

    View full-size slide

  24. Из чего состоит приемлимый пайплайн?
    27
    — Сборка
    Давайте немного о CI части

    View full-size slide

  25. Из чего состоит приемлимый пайплайн?
    28
    — Сборка
    — Статический анализ (линтинг)
    Давайте немного о CI части

    View full-size slide

  26. Из чего состоит приемлимый пайплайн?
    29
    — Сборка
    — Статический анализ (линтинг)
    — Тесты
    Давайте немного о CI части

    View full-size slide

  27. Из чего состоит приемлимый пайплайн?
    30
    — Сборка
    — Статический анализ (линтинг)
    — Тесты
    Давайте немного о CI части
    А вы уверены в порядке?

    View full-size slide

  28. А из чего состоит хороший пайплайн?
    31
    — Сборка
    — Статический анализ (линтинг)
    — Тесты
    — Безопасность?
    Давайте немного о CI части

    View full-size slide

  29. А из чего состоит хороший пайплайн?
    32
    — Сборка
    — Статический анализ (линтинг)
    — Тесты
    — Безопасность?
    — Чистка «соплей»
    Давайте немного о CI части

    View full-size slide

  30. Кратко об истории
    создания нашего
    пайплайна
    33

    View full-size slide

  31. 2019 год
    34
    — У нас был bamboo
    Мы начали разработку чат-бота втроем

    View full-size slide

  32. 2020 год
    37
    — Число репозиториев перешагнуло за 20
    — Появился gitlab

    View full-size slide

  33. О двойной
    докеризации
    38

    View full-size slide

  34. О «двойной докеризации»
    40
    Это важно
    Очень часто разработчики делают пайплайн так:
    do-all-things:
    stage: test
    image: python:3.8-slim
    script:
    - apt-get update -y
    - apt-get install -y pgdev kerberos enchant-2
    - pytest .

    View full-size slide

  35. А что тут не так?
    41
    Сборка неповторяема / не идемпотентна
    do-all-things:
    stage: test
    image: python:3.8-slim
    script:
    - apt-get update -y
    - apt-get install -y pgdev kerberos enchant-2
    - pytest .

    View full-size slide

  36. А что тут не так?
    42
    Дублируем Dockerfile
    do-all-things:
    stage: test
    image: python:3.8-slim
    script:
    - apt-get update -y
    - apt-get install -y pgdev kerberos enchant-2
    - pytest .

    View full-size slide

  37. Что делаю я в пайплайне?
    43
    Итак, «двойная докеризация»
    almost-real-pipeline:
    script:
    ...
    - docker build . -t fancy-tag
    - docker run -t fancy-tag:latest bash -c "pytest ."
    ...

    View full-size slide

  38. В чём помогает «двойная» докеризация?
    44
    Специфика проектов остается
    в этих проектах и не протекает
    в общий пайплайн
    В изоляции

    View full-size slide

  39. В чём помогает «двойная» докеризация?
    45
    Специфика проектов остается
    в этих проектах и не протекает
    в общий пайплайн
    В изоляции
    Все дубликаты, характерные
    для devops автоматизаций
    можно устранить!
    В устранении
    дупликации

    View full-size slide

  40. В чём помогает «двойная» докеризация?
    46
    Специфика проектов остается
    в этих проектах и не протекает
    в общий пайплайн
    В изоляции
    Все дубликаты, характерные
    для devops автоматизаций
    можно устранить!
    В устранении
    дупликации
    Ускоряем обновление на
    новые версии с помощью
    низкой связанности
    В ускорении!

    View full-size slide

  41. Есть проблемы
    47
    — Во-первых, вам нужно коннектиться к докер демону (у gitlab
    хороший гайд) из «второго слоя»
    https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
    «Двойная докеризация» бьет в псину

    View full-size slide

  42. Есть проблемы
    48
    — Во-первых, вам нужно коннектиться к докер демону (у gitlab
    хороший гайд) из «второго слоя»
    https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
    — Копирование файлов и монтирование — боль
    «Двойная докеризация» бьет в псину

    View full-size slide

  43. Есть проблемы
    49
    — Во-первых, вам нужно коннектиться к докер демону (у gitlab
    хороший гайд) из «второго слоя»
    https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
    — Копирование файлов и монтирование — боль ✅
    «Двойная докеризация» бьет в псину

    View full-size slide

  44. Есть проблемы
    50
    — Во-первых, вам нужно коннектиться к докер демону (у gitlab
    хороший гайд) из «второго слоя»
    https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
    — Копирование файлов и монтирование — боль ✅
    — Связываться с сайд контейнерами из services непросто
    «Двойная докеризация» бьет в псину

    View full-size slide

  45. Наконец уже к
    пайплайну!
    51

    View full-size slide

  46. Примеры к презентации, код
    52
    https://github.com/xfenix/pycon2022

    View full-size slide

  47. Ключевые особенности
    53
    — «Двойная докеризация» 🐳 🐳
    Это всё на Gitlab

    View full-size slide

  48. Ключевые особенности
    54
    — «Двойная докеризация» 🐳 🐳
    — Централизация
    Это всё на Gitlab

    View full-size slide

  49. Ключевые особенности
    55
    — «Двойная докеризация» 🐳 🐳
    — Централизация
    — Пресеты
    Это всё на Gitlab

    View full-size slide

  50. Ключевые особенности
    56
    — «Двойная докеризация» 🐳 🐳
    — Централизация
    — Пресеты
    — Использование include, reference, template для избегания
    основной боли пайплайнов: дупликации кода
    Это всё на Gitlab

    View full-size slide

  51. Ключевые особенности
    57
    — «Двойная докеризация» 🐳 🐳
    — Централизация
    — Пресеты
    — Использование include, reference, template для избегания
    основной боли пайплайнов: дупликации кода
    — Тестируем и отправляем в продакшн один и тот же образ
    Это всё на Gitlab

    View full-size slide

  52. Лейаут: центральный репозиторий
    59

    View full-size slide

  53. Лейаут: репозиторий сервиса
    60
    variables:
    DSN_VARIABLE_NAME: "NV_CHB_DB_DSN_SHARED"
    PYLINT_ARGS: "--load-plugins pylint_django base core"
    include:
    - project: "chat/misc/generic-cicd"
    file: "common/group-vars-chat-bot.yml"
    - project: 'chat/misc/generic-cicd'
    file: 'ci/python-postgres.yml'

    View full-size slide

  54. Вот теперь точно
    пайплайн
    61

    View full-size slide

  55. Шаг 1
    63
    Проверяем переменные
    .vars-check-job:
    extends: .default-job
    stage: .pre
    before_script:
    - set -u

    View full-size slide

  56. Шаг 1
    64
    Проверяем переменные
    .vars-check-job:
    extends: .default-job
    stage: .pre
    before_script:
    - set -u
    check-front-necessary-variables:
    extends: .vars-check-job
    script:
    - echo $PROJECT_SLUG $MAIN_USER

    View full-size slide

  57. Шаг 2
    65
    Собираем образ и пушим в registry. Здесь есть разные важные детали
    .build-docker-job:
    stage: build
    variables:
    STORAGE_DRIVER: vfs
    script:
    - set -x
    ...
    - buildah bud --jobs=0 --format=docker -t $TAG_FOR_CI ./Dockerfile
    - buildah push $DOCKER_TAG_FOR_CI

    View full-size slide

  58. 66
    Примечание
    set –x
    в каждом пайплане вам будет полезен!

    View full-size slide

  59. Шаг 2: как генерируются теги
    67
    Деталь 1
    VERSION=$(if [[ $CI_COMMIT_TAG ]]; then
    echo ${CI_COMMIT_TAG/v}; else echo latest; fi)
    NAME=$(if [[ $CI_COMMIT_TAG ]]; then
    echo release; else echo $CI_COMMIT_BRANCH; fi)
    $REGISTRY/$PROJECT/$CI_PROJECT_NAME/$NAME:$VERSION
    # comany-hub.com/service-name/release:1.2.3
    # comany-hub.com/service-name/master:latest

    View full-size slide

  60. Шаг 2: кладем в образ мета-инфо
    68
    Деталь 2
    echo "{"version": "$DOCKER_IMAGE_VERSION", "service": "$CI_PROJECT_NAME",
    "project": "$PROJECT_SLUG"}" > status.json

    View full-size slide

  61. Шаг 3: готовим окружение
    69
    Статические проверки (линтинг): подготовка
    script:
    ...
    - >-
    podman run
    -v $TOOLSET_PATH:/$TOOLSET_PATH
    -t $DOCKER_TAG_FOR_CI
    bash -c "if id my-user > /dev/null 2>&1; then runAs='--user my-user'; fi &&
    pip3 install $runAs -U 'pylint==2.12.2' 'mypy==0.942' 'isort==5.10.1'
    'black==22.3.0’ &&
    ...

    View full-size slide

  62. Шаг 3: pylint
    70
    Статические проверки (линтинг): pylint с поддержкой auto-discovery механизма, fail-under, baseline
    /$TOOLSET_SCRIPTS_PATH/lint.py
    -project_dir=$PROJ_DIR_IN_DOCKER
    -action=lint_pylint
    -pylint_args='$PYLINT_ARGS'
    -pylint_score=${PYLINT_SCORE:-10.0} &&

    View full-size slide

  63. Шаг 3: mypy
    71
    Статические проверки (линтинг): mypy с поддержкой baseline, «fail under»
    /$TOOLSET_SCRIPTS_PATH/lint.py
    -project_dir=$PROJ_DIR_IN_DOCKER
    -action=lint_mypy
    -mypy_score=${MYPY_SCORE:-0} &&

    View full-size slide

  64. Шаг 3: остальное
    72
    Банально
    python -m isort **/*.py --check --diff &&
    python -m black **/*.py --check --diff

    View full-size slide

  65. Шаг 3
    73
    Что можно добавить ещё из полезного
    — Radon (метрики кода)

    View full-size slide

  66. Шаг 3
    74
    Что можно добавить ещё из полезного
    — Radon (метрики кода)
    — Eradicate (обнаружение комментированого кода)

    View full-size slide

  67. Шаг 3
    75
    Что можно добавить ещё из полезного
    — Radon (метрики кода)
    — Eradicate (обнаружение комментированого кода)
    — Vulture (обнаружение мертвого кода)

    View full-size slide

  68. Шаг 3
    76
    Что можно добавить ещё из полезного
    — Radon (метрики кода)
    — Eradicate (обнаружение комментированого кода)
    — Vulture (обнаружение мертвого кода)
    — Prospector (метрики кода, можно заменить pylint в теории)

    View full-size slide

  69. Шаг 3
    77
    Что можно добавить ещё из полезного
    — Radon (метрики кода)
    — Eradicate (обнаружение комментированого кода)
    — Vulture (обнаружение мертвого кода)
    или
    — Prospector (разные тулы)
    — Pylama — все вместе (разные тулы)

    View full-size slide

  70. Шаг 3: за рамками python
    78
    — Hadolint
    — Kube-linter

    View full-size slide

  71. Шаг 4
    80
    Тестирование
    ...
    services:
    - name: postgres:14-alpine
    command: ["postgres", "-c",
    "shared_buffers=256MB", "-c", "max_connections=420"]
    - name: keydb:x86_64_v6.3.0
    script:
    - export BEFORE_TESTS_HOOK="${BEFORE_TESTS_HOOK:-alembic upgrade head}"
    ...
    - export DB_IP_ADDR=$(cat /etc/hosts | grep "runner-" | awk '{print $1}')

    View full-size slide

  72. Шаг 4
    81
    Тестирование
    - podman run
    ...
    --add-host="$DB_HOST:${TEST_DB_IP_ADDR}"
    --add-host="$SENTINEL_NAME-0:${TEST_DB_IP_ADDR}"
    -e $DSN_NAME=postgresql://$PG_USER:$PG_PASS@$DB_HOST:$DB_PORT/$PG_DB
    bash -c "eval ${BEFORE_TESTS_HOOK:-} && pytest ."
    ...

    View full-size slide

  73. Шаг 4
    82
    Примечания
    — Мы используем для линтинга, тестов и дальнейшего деплоя
    один и тот же образ

    View full-size slide

  74. Шаг 4
    83
    Примечания
    — Мы используем для линтинга, тестов и дальнейшего деплоя
    один и тот же образ
    — Лейаут докер образов такой: есть имя master и у него 1 тег
    latest, а так же release и теги — версии. Т.е. выглядит так:
    release:1.2.3

    View full-size slide

  75. Шаг 5
    84
    Б — безопасность

    View full-size slide

  76. Шаг 5
    85
    Б — безопасность

    View full-size slide

  77. Шаг 5
    86
    Б — безопасность

    View full-size slide

  78. Шаг 5
    87
    Б — безопасность

    View full-size slide

  79. Шаг 5
    88
    Б — безопасность

    View full-size slide

  80. Шаг 5
    89
    Б — безопасность

    View full-size slide

  81. Шаг 6
    90
    Пушим образ
    publish-docker-image:
    script:
    - set -x
    - !reference [.prepare-build-tag, script]
    - echo ${FULL_DOCKER_TAG?-Undefined FULL_DOCKER_TAG var}
    - echo ${DOCKER_TAG_FOR_CI?-Undefined DOCKER_TAG_FOR_CI var}
    - !reference [.podman-login, script]
    - skopeo copy docker://$DOCKER_TAG_FOR_CI docker://$FULL_DOCKER_TAG

    View full-size slide

  82. А что с CD?
    91

    View full-size slide

  83. Автоверсионирование
    92
    Под TBD
    — GitVersion
    — Semantic-release
    — …

    View full-size slide

  84. Автоверсионирование
    93
    Под TBD
    — GitVersion
    — Semantic-release
    — …
    — Напишем свое!

    View full-size slide

  85. Автоверсионирование
    94
    Кратко
    — Свой скрипт

    View full-size slide

  86. 95
    РИСУНОК
    ПИТОН КОДА

    View full-size slide

  87. Автоверсионирование
    96
    Кратко
    — Свой скрипт
    — Ищем нужный маркер ветки

    View full-size slide

  88. Автоверсионирование
    97
    Кратко
    — Свой скрипт
    — Ищем нужный маркер ветки
    — Бампаем нужную часть
    semver

    View full-size slide

  89. Автоверсионирование
    98
    Кратко
    — Свой скрипт
    — Ищем нужный маркер ветки
    — Бампаем нужную часть
    semver
    auto-semver:
    stage: deploy
    script:
    - python -m pip install semver GitPython
    - python ./auto-semver.py version
    rules:
    - if: $CI_COMMIT_BRANCH == "master"

    View full-size slide

  90. И заканчивая
    99
    Надо бы за собой почистить
    — Можно чистить в конце пайплайна (здесь есть проблемы)

    View full-size slide

  91. И заканчивая
    100
    Надо бы за собой почистить
    — Можно чистить в конце пайплайна (здесь есть проблемы)
    — Можно чистить с помощью scheduled CI раз в сутки

    View full-size slide

  92. И заканчивая
    101
    Надо бы за собой почистить
    — Можно чистить в конце пайплайна (здесь есть проблемы)
    — Можно чистить с помощью scheduled CI раз в сутки
    — Раз в сутки ходим и чистим release имя, удаляя все кроме N
    (3-5 обычно достаточно) последних версий/тегов

    View full-size slide

  93. Возвращаемся назад!
    103
    У нас есть проблема с чисткой
    — Пишем свой скрипт

    View full-size slide

  94. Возвращаемся назад!
    104
    У нас есть проблема с чисткой
    — Пишем свой скрипт
    — В репозиторий добавляем два
    «курсор»-тега: release-prod, release-prev

    View full-size slide

  95. Возвращаемся назад!
    105
    У нас есть проблема с чисткой
    — Пишем свой скрипт
    — В репозиторий добавляем два
    «курсор»-тега: release-prod, release-prev
    — При каждом деплое мы просто
    удаляем старые и вешаем release-
    prod/release-prev теги «на тот тег»,
    который деплоим

    View full-size slide

  96. Возвращаемся назад!
    106
    У нас есть проблема с чисткой
    — Пишем свой скрипт
    — В репозиторий добавляем два
    «курсор»-тега: release-prod, release-prev
    — При каждом деплое мы просто
    удаляем старые и вешаем release-
    prod/release-prev теги «на тот тег»,
    который деплоим
    auto-semver:
    stage: deploy
    script:
    - python -m pip install semver GitPython
    - python ./auto-version.py mark
    rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'

    View full-size slide

  97. Немного про deploy
    107
    — Если у вас docker/docker-compose, то вы делаете pull/restart/up/start

    View full-size slide

  98. Немного про deploy
    108
    — Если у вас docker/docker-compose, то вы делаете pull/restart/up/start
    — Если у вас helm, всё чуть сложнее (но об этом в кулуарах, смотрите
    на гитхабе и не стесняйтесь писать мне лично)

    View full-size slide

  99. Немного про deploy
    109
    — Если у вас docker/docker-compose, то вы делаете pull/restart/up/start
    — Если у вас helm, всё чуть сложнее (но об этом в кулуарах, смотрите
    на гитхабе и не стесняйтесь писать мне лично)
    — И здесь есть куча всяких особенностей с формированием окружений
    под разные среды: staging, production, dev

    View full-size slide

  100. Денис Аникин
    https://xfenix.ru/
    Спасибо!

    View full-size slide