Оглавление

Docker

Что такое контейнер?

Linux namespaces

Cgroups

Из каких компонентов состоит Docker?

Docker daemon

Как докер запускается на разных ОС?

Docker image (образ)

Docker Registry (реестр)

Dockerfile

Контейнеры

Сеть

Файловая система

Запуск своего приложения внутри Docker

Приложение

Соберем образ с помощью команды docker build.

База данных

Соединяем

Список команд

Порядок действий

Docker

Здесь на помощь нам приходит следующая технология — контейнеризация. Она позволяет запускать процессы в изолированном окружении внутри ОС. Docker — одна из них. А еще docker — это отличный способ «завернуть» всю установку своего приложения в один файл и запускать на любой операционной системе. **Docker (докер)** — это платформа для разработки и запуска контейнеров. Докер позволяет создавать контейнеры, автоматизирует их запуск и управляет жизненным циклом.

Что такое контейнер?

Контейнер — это набор запущенных процессов, находящийся внутри собственного «окружения». Кажется, будто они запущены внутри отдельной операционной системы. Но на самом деле это не так. В ядре Linux есть два инструмента, которые позволяют изолировать приложение, не прибегая к «дорогой» (по ресурсам) виртуализации, — это **namespaces** и **cgroups.**

Linux namespaces

это абстракция над ресурсами в операционной системе. В настоящее время существует семь типов пространств имен (namespaces): Cgroups, IPC, Network, Mount, PID, User, UTS. Каждое из них отвечает за свою область ресурсов, например, Mount namespace включает файлы и каталоги в системе, PID содержит ID процессов и так далее.

Cgroups

это также встроенный механизм ядра Linux. Он отвечает за управление потреблением ресурсов. С помощью него можно задать, сколько может потреблять CPU или оперативной памяти конкретный процесс.

Объединяя две эти технологии, мы получаем отличный способ изолировать приложение и управлять потреблением ресурсов. Таким образом, приложения внутри контейнеров видят только свои процессы, свои файлы, свою сеть и так далее.

Из каких компонентов состоит Docker?

Docker daemon

это сервис, через который осуществляется всё взаимодействие с контейнерами: создание и удаление, запуск и остановка. Этот сервис работает в фоновом режиме и получает команды от интерфейса командной строки или API. **Docker client (клиент)** — это интерфейс командной строки (программа) для управления Docker daemon. Мы пользуемся этим клиентом, когда создаем и разворачиваем контейнеры, а клиент отправляет эти запросы в Docker daemon.

Как докер запускается на разных ОС?

Так как Docker использует ядро Linux, то ему нужна именно эта операционная система. Поэтому на ОС Linux Docker запускается «нативно» и никаких дополнительных установок не нужно. В случае с ОС Windows и Mac OS для докера потребуется виртуальная машина с ОС Linux, внутри которой уже устанавливается deamon, как в случае с обычной установкой. А Docker client устанавливается на самой операционной системе (не в виртуалке). Но нам об этом думать не нужно — установщик сделает всё за нас, а само использование и управление Docker будет одинаковым на всех системах.

Docker image (образ)

это неизменяемый файл (образ), из которого разворачиваются контейнеры. Приложения упаковываются именно в образы, из которых потом уже создаются контейнеры. Для аналогии можно привести пример с установкой игры на компьютер. Мы покупаем диск с игрой, но чтобы в нее поиграть, нам нужно сначала установить игру. Так вот, образ можно представить как диск, когда контейнер — это установленная и запущенная игра на конкретном компьютере.


                    
docker images # список образов на машине 
docker pull <образ> # скачать образ из репозитория
docker rmi <образ> # удалить образ с машины 
                

Docker Registry (реестр)

это репозиторий с докер-образами. Разработчики создают образы своих программ и выкладывают их в репозиторий для скачивания и использования. Распространенный публичный репозиторий — Docker Hub. В нем собраны образы множества популярных программ или платформ: базы данных, веб-серверы, компиляторы, операционные системы и так далее. Также можно создать свой приватный репозиторий, например, внутри компании. Разработчики будут размещать там образы для использования всей компанией.

Dockerfile

это инструкция для сборки образа. Представляет собой простой текстовый файл, содержащий по одной команде в каждой строке. В нем указываются все программы, зависимости и образы, которые нужны для разворачивания образа. Для примера рассмотрим Dockerfile, который мы будем использовать далее в этой статье, чтобы развернуть собственное приложение.


                    
FROM python:3
COPY main.py /
CMD [ "python", "./main.py" ]
                

Первая строчка означает, что за основу мы берем образ с названием python версии 3. Это называется базовым образом. Docker найдет его в Docker Registry, скачает и будет использовать за основу. Вторая строчка означает, что нужно скопировать файл `main.py` в корень файловой системы контейнера. Третья строчка означает, что нужно запустить Python и передать ему в качестве параметра файл `main.py`. Чтобы создать образ из Dockerfile, нужно запустить команду `docker build`.


                    
docker build -t my-image . # <- точка в конце означает путь до директории с файлами
#             ^  этим параметром образу дается название
                

Контейнеры

Запускается контейнер с помощью команды `docker run` . В этот момент: - инициализируется файловая система; - выдается IP-адрес внутри сети докера; - запускается команда (**CMD из Dockerfile**), и ей выдается PID (идентификатор процесса): 1. Если главный процесс по какой-либо причине остановится, то весь контейнер будет «убит», а также исчезнут все другие процессы, которые были запущены внутри этого контейнера.


                    docker run my-image # <- my-image — название образа
                

Сеть

Когда создается контейнер, ему присваивается адрес и появляется возможность для доступа в интернет без всяких дополнительных усилий с вашей стороны. Для этого в Docker используется сетевой мост (**bridge**), обычно его имя в системе — docker0. Для каждого контейнера создается свой виртуальный сетевой интерфейс, он и подключается к сети при помощи **bridge**.

Так же есть типы: - **host** — как видно из названия, в этом случае подключение происходит к сети хоста. Это значит, что контейнер может общаться с сервисами, которые запущены на локальном интерфейсе так, как если бы он был запущен прямо на хосте. - **none** — означает, что сети нет. Есть и другие типы, но мы их затрагивать не будем. Можно создавать свои сети для того, чтобы контейнеры «видели» друг друга. Для этого используется команда `docker network`.


                    
docker network ls # посмотреть созданные сети
docker network inspect <название> # посмотреть детальную информацию о конкретной сети
docker network create <название> # создать сеть
docker run --network=<название сети> <image> # запустить контейнер внутри сети
                

Файловая система

Каждый образ состоит из слоев. Слой — это добавление очередных файлов поверх предыдущего слоя. Первый слой в Docker — это базовый образ scratch. Все остальные образы в Docker построены от него с помощью команд из Dockerfile. Каждая отдельная команда в Dockerfile — это новый слой, который состоит из файлов, добавленных этой командой.


                                                   
Рассмотрим Dockerfile для Python-приложения:

# Базовый образ, который тоже состоит из слоев
FROM python:3.10

# Новый слой
WORKDIR /code
# Новый слой
COPY requirements.txt .
# Новый слой
RUN pip install -r requirements.txt
# Новый слой
COPY app.py .
# Новый слой
COPY migrations migrations
# Новый слой
COPY docker_config.py default_config.py

# Новый слой
CMD flask run -h 0.0.0.0 -p 80
                

Когда мы запускаем контейнер, на слои образа наслаивается еще один — слой контейнера. Здесь будут храниться все файлы и изменения, которые контейнер сделает во время своей работы. Например, образ ubuntu состоит из 4 слоев:

Запуск своего приложения внутри Docker

Наше приложение состоит из двух компонентов: - API на Flask, - база данных. Каждый из этих компонентов будет запущен внутри отдельного Docker-контейнера.

Приложение

Для того чтобы нам собрать свой Python-образ, потребуется следующий Dockerfile:


                    
# Базовый образ Python (в нем уже установлен Python, pip и прочее)
FROM python:3.10

# Создаем директорию /code и переходим в нее
WORKDIR /code
# Копируем файл с зависимостями
COPY requirements.txt .
# Устанавливаем через pip зависимости
RUN pip install -r requirements.txt
# Копируем код приложения
COPY app.py .
# Копируем код приложения
COPY migrations migrations
# Копируем специальный конфиг и подменяем дефолтный
COPY docker_config.py default_config.py

# Указываем команду, которая будет запущена командой docker run
CMD flask run -h 0.0.0.0 -p 80
                

Соберем образ с помощью команды docker build.

# В директории с проектом запускаем команду docker build -t flask-app .


                    
# В директории с проектом запускаем команду
docker build -t flask-app .
                

База данных

Здесь всё гораздо проще: образ для PosgreSQL уже есть в Docker Hub, нам не потребуется собирать его с помощью Dockerfile. [Ссылка на образ](https://hub.docker.com/_/postgres). Достаточно будет просто запустить команду.


                    
docker run --name postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=flask-app -d postgres
# Параметр --name дает название контейнеру
# Параметр -d запускает контейнер в фоновом режиме
# Параметр -e – прокидывает переменные окружения внутрь контейнера
# Для образа postgres существует много переменных окружения, с помощью которых
# его можно настраивать. Но нам потребуется два:
# POSTGRES_PASSWORD – пароль для пользователя postgres
# POSTGRES_DB – название базы данных внутри сервера postgres
                

Соединяем

Для того чтобы контейнеры видели друг друга, нам потребуется объединить их в одну сеть. Поэтому нам нужно будет ее создать.


                    # Создаем сеть
docker network create flask-app

# Запускаем контейнер с БД внутри этой сети
docker run --network flask-app --network-alias postgres --name postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=flask-app -d postgres
# Параметр --network подключает контейнер к сети
# Параметр --network-alias дает сетевое имя контейнеру внутри этой сети 
# Теперь к нему можно обращаться как к postgres

# Запускаем контейнер с API
docker run --network flask-app -p 8000:80 --name flask-app -d flask-app
# Параметр -p – проброс портов. Внутри контейнера flask слушает на порту 80
# Этим параметром мы совместили 8000 порт на хосте с 80 портом внутри контейнера
# Поэтому теперь мы можем открыть браузер, вбить http://localhost:8000 
# и попасть на 80 порт контейнера

# Применяем миграции к БД, подключившись внутрь самого контейнера
docker exec -it flask-app /bin/bash
# Запускаем внутри контейнера миграции:
root $ > flask db upgrade
# На этом всё, можно выходить из контейнера с помощью ctrl+D
                

Список команд


                    
* ps aux – вывести списком все текущие процессы в системе
* top – интерактивная консоль всех текущих процессов в системе
* dig sky.pro – узнать IP-адрес sky.pro
* dig MX sky.pro – узнать почтовые адреса sky.pro
* htop – запущенные в системе процессы
* netstat -tulpn – активные порты
* aptitude search packet-name – поиск пакета packet-name

* add-apt-repository ppa:deadsnakes/ppa – установить python в ubuntu
* apt install python-pip – to install pip

* journalctl -u unit_name – shows a system log for the app_name
* journalctl -u unit_name -f – shows a system log last records for the app_name

* systemctl start unit_name – starts a service immediately
* systemctl stop unit_name – stops a service
* systemctl daemon-reload – reloads daemon data
* systemctl enable unit_name – to start a service at next reboot
* systemctl disable unit_name – to disable a service at next reboot

* etc/ssh/sshd_config – системные настройки
* sudo service ssh start – обновить системы настройки

Виртуальное окружение:
* python3.10 -m venv env – install virtual environment
* virtualenv env – создание виртуального окружения

* rm -r dir – удалить непустой каталог

- usermod -aG sudo username – дать права администратора пользователю ipotemkin (? проверить работу)
                

Порядок действий


                    
#установка виртуального окружения
pip3 install virtualenv
virtualenv -p python3.8 env

#активация окружения
. env/bin/activate

#старт приложения
systemctl start flask-app
systemctl daemon-reload

#просмотр журнала
journalctl -u flask-app -f -n 1000

sudo lsof -i :5000
kill 7426

#создание файла для настроек сервиса
vim /etc/systemd/system/flask-app.service

#копирование файлов
scp -r course_work_2_1 stasy23@51.250.27.163:python_3/

#файл flask-app.service
[Unit]
Description=Flask-app
After=network.target

[Service]
WorkingDirectory=/home/stasy23/python_3/
ExecStart=/home/stasy23/python_3/env/bin/python -m flask run -h 0.0.0.0 -p 80
Restart=always

[Install]
WantedBy=multi-user.target
                

Оглавление

Docker volumes

Docker Compose

Порядок запуска Docker Compose

Volumes

Деплой через Docker Compose на сервере

Создаем отдельный docker-compose-server.yaml

Устанавливаем Docker на сервере

Копируем docker-compose-server.yaml на сервер

Docker volumes

При создании контейнеру выделяется слой, в котором он хранит свои файлы


                                                                                   
Файлы слоя хранятся в /var/lib/docker/overlay/<id слоя>.
Узнать id слоя можно через команду docker inspect <id или имя контейнера>.

Docker в системах Windows или Mac OS запущен через виртуальную машину. 
Поэтому если вы будете искать эту директорию через проводник или консоль, то вы ничего не найдете.

Для того чтобы «зайти» внутрь этой виртуальной машины и посмотреть на файлы, нужно использовать команду
docker run -it --privileged --pid=host justincormack/nsenter1.

Так, с помощью привилегированного контейнера, вы попадете внутрь виртуальной машины, в которой уже можно смотреть на структуру файлов Docker.
ls /var/lib/docker/overlay/
                        

**Что происходит с файлами, когда контейнер удаляется?** Весь слой будет удален, а файлы будут потеряны.

**Почему так происходит?** Контейнер создается из образа, в котором есть всё для начала его работы. Но там не хранится и тем более не изменяется ничего важного. В любой момент приложение в контейнере может быть завершено, а контейнер уничтожен, и это нормально. Контейнер отработал — выкидываем его и собираем новый. Если пользователь загрузил в приложение картинку, то при замене контейнера она удалится. В идеальном мире Docker используют только для запуска **stateless**-приложений, которые не читают и не сохраняют данные о своем состоянии и готовы в любой момент завершиться. Однако в реальности большинство программ относятся к категории **stateful**, то есть требуют сохранения данных между перезапусками.

**Как это предотвратить?** В Docker есть специальный инструмент для этого — **Docker volumes.** Это способ хранения данных в выделенной директории на хосте (там, где запущен Docker). С помощью volumes мы можем «сохранить» файлы в конкретной директории, и при удалении контейнера все файлы сохранятся.

В основном используется 2 типа : - Отдельная сущность volume — директория/том, хранящийся в «недрах» Docker (в месте `/var/lib/docker/volumes`). - Монтирование директории с хоста — можем пробросить внутрь контейнера папку на компьютере. Принципиально они почти ничем не отличаются, разве что на операционных системах Windows и Mac OS монтирование директории работает через виртуальную машину.

Основные команды

**Что можно хранить в Volume** - данные БД, - файлы, добавляемые пользователем, - файлы конфигурации (подменять стандартные), - при разработке пробрасывать код внутрь контейнера.


                            
# Посмотреть созданные volume
docker volume ls
# Создать volume
docker volume create
# Посмотреть детальную информацию
docker volume inspect <название volume> 
# Удалить volume
docker volume rm <название volume> 
# Удалить все не используемые в данный момент volume
docker volume prune 

# Проброс volume в контейнер
docker volume create test
docker run -v test:/dir_in_container <образ>
# Название volume–^ ^–Название директории внутри контейнера

# Проброс директории с хоста в контейнер
docker run -v /host_dir:/dir_in_container -d nginx
# Абсолютный путь до папки–^              ^-директория внутри контейнера
                        

Docker Compose

В предыдущей лекции мы выполнили много команд для того, чтобы развернуть приложение. Чем больше компонентов в приложении, тем дольше это настраивать. Но в Docker существует специальный инструмент для развертывания и настройки целого комплекса контейнеров — Docker Compose. С помощью него мы опишем всё наше приложение в одном файле.


                                                                                   
docker-compose.yaml

version: "3.9"
services:
  api:
    build:
      context: .
    ports:
    - 8000:80
  postgres:
    image: postgres:latest
                        

В этом файле описывается весь состав приложения: - контейнеры, - сети, - volumes — тома, - порядок запуска контейнеров.


                                                                                   
Секция services

version: "3.9"
services:
  api:
    build:
      context: .
    ports:
    - 8000:80
  postgres:
    image: postgres:latest
                        

Внутри этой секции описываются все контейнеры, которые должны быть запущены: - api, postgres — название сервиса; - секция build: если нужно собрать образ перед запуском; - секция image: сразу используем готовый образ для запуска контейнера; - ports — проброс портов.

**Переменные окружения** Многие контейнеры могут настраиваться через переменные окружения. Мы можем их указать с помощью специальной секции env.


                            
version: "3.8"
services:
  api:
    build:
      context: .
    ports:
    - 8000:80
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
                        

Основные команды


                            
# Запустить всё приложение (контейнеры, сети и т. д.)
docker-compose up
# То же самое, только в режиме демона
docker-compose up -d
# Остановить контейнеры
docker-compose stop
# Запустить контейнеры
docker-compose start
# Остановить контейнеры и удалить все компоненты
docker-compose down
# Собрать необходимые образы, но ничего не запускать
docker-compose build
# Скачать необходимые образы
docker-compose pull
# Посмотреть логи сервисов
docker-compose logs
                        

Порядок запуска Docker Compose

Перед тем, как приложение должно быть запущено, нам нужно дождаться, чтобы PostgreSQL запустилась. После этого мы можем применить миграции и только потом можем запустить приложение. К счастью, у Docker Compose есть все необходимые для этого инструменты.

Здесь мы добавили еще один контейнер — migrations. Он должен запуститься после того, как будет запущена PostgreSQL. Контейнер api должен запуститься только после успешного завершения контейнера migrations.


                                                                                   
depends_on

version: "3.8"
services:
  api:
    build:
      context: .
    ports:
    - 8000:80
    depends_on:
      postgres:
        condition: service_healthy
      migrations:
        condition: service_completed_successfully
  migrations:
    build:
      context: .
    depends_on:
      postgres:
        condition: service_healthy
    command: flask db upgrade
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
                        

Эта секция позволяет указать порядок запуска сервисов. Также можно указать условия: - **service_started** — просто порядок запуска; - **service_healthy** — запустить только после того, как контейнер будет работать (пройдет healthcheck); - **service_completed_successfully** — запустить только после того, как успешно завершится другой контейнер.

Эта секция позволяет указать команду, с помощью которой будет определяться, что контейнер корректно запущен и готов к работе: - **test** — команда для проверки; - **interval** — время между проверками; - **timeout** — сколько времени ожидать выполнения проверки; - **retries** — количество провальных проверок подряд, чтобы пометить контейнер unhealthy.


                                                                                   
healthcheck

version: "3.8"
services:
  api:
    build:
      context: .
    ports:
    - 8000:80
    depends_on:
      postgres:
        condition: service_healthy
      migrations:
        condition: service_completed_successfully
  migrations:
    build:
      context: .
    depends_on:
      postgres:
        condition: service_healthy
    command: flask db upgrade
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
                        

Volumes


                            
version: "3.8"
services:
  api:
    build:
      context: .
    ...
    volumes:
      - ./docker_config.py:/code/default_config.py
  migrations:
    build:
      context: .
    ...
    volumes:
      - ./docker_config.py:/code/default_config.py
    command: flask db upgrade
  postgres:
    image: postgres:latest
    ...
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
                        

Деплой через Docker Compose на сервере

Docker ID — это аналог username. Нужно придумать свое уникальное имя. После регистрации открывается такой интерфейс:


                            **Docker registry**

Мы можем скопировать директорию проекта на сервер и запустить  `docker-compose up`.

Однако так мы будем нагружать наш сервер сборкой образа, которую на самом деле можно не делать. Вместо этого мы соберем наш образ локально на компьютере и отправим в Docker Hub — бесплатный registry. Для этого нам потребуется в нем зарегистрироваться. Регистрируемся [здесь](https://hub.docker.com).
                        

Кликаем на синюю кнопку Create repository.

После этого нам потребуется собрать и отправить образ в registry.


                            
docker login # далее вводим логин и пароль, созданные при регистрации
# здесь нужно указать <имя своего пользователя>/<название репозитория>:<тег первой версии>
docker build -t sermalenk/flask-app:version-1 . 
docker push sermalenk/flask-app:version-1 # отправляем образ в Docker Hub
                        

Создаем отдельный docker-compose-server.yaml

Нам понадобится отдельный docker-compose.yaml файл, в котором мы изменим build на image, собранный на предыдущем этапе.


                            
version: "3.8"
services:
  api:
    image: sermalenk/flask-app:version-1
    ports:
    - 80:80
    depends_on:
      postgres:
        condition: service_healthy
      migrations:
        condition: service_completed_successfully
    volumes:
      - ./docker_config.py:/code/default_config.py
  migrations:
    image: sermalenk/flask-app:version-1
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./docker_config.py:/code/default_config.py
    command: flask db upgrade
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
                        

Наш итоговый Dockerfile для сервера

Устанавливаем Docker на сервере

Подключаемся через SSH к серверу. Далее идем по [официальной инструкции](https://docs.docker.com/engine/install/ubuntu/).


                            
sudo su                                                       
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Для установки docker
 echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt install docker-ce docker-ce-cli containerd.io

curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
#Для подключения docker-compose

# Компонент докера
chmod +x /usr/local/bin/docker-compose
# Права на исполнение
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
# Делаем ссылку
                        

Копируем docker-compose-server.yaml на сервер

Команда выполняется на своем компьютере.


                            
scp <путь до проекта>/docker_config.py <имя>@<адрес>:.
scp <путь до проекта>/docker-compose-server.yaml <имя>@<адрес>:docker-compose.yaml

                        

Открываем адрес нашего сервера, который мы создали в предыдущем уроке, и проверяем корректную работу.


                                       
Снова подключаемся через SSH к серверу.

sudo su
docker-compose up -d
                        

Оглавление

Что такое CI/CD?

Основные процессы

Сборка

Ручное или автоматическое тестирование.

Релиз

Развертывание

Поддержка и мониторинг.

Планирование.

Существующие системы для CI/CD

Рассмотрим системы, участвующие в процессах CI/CD:

Как мы построим процесс CI/CD?

GitHub Actions

Workflows

Events

Jobs

Actions

Runner

Создаем первый workflow

Далее мы можем приступить к сборке Docker-образа.

Переменные окружения

Секретные данные

Docker Hub Access Tokens

Нажимаем получить токен

Далее создаем в GitHub secrets два секрета:

GitHub Actions CD

Конфигурация

Загрузка файлов на сервер

Запуск приложения через GitHub Actions

Конечный docker-compose-ci.yaml

Конечный файл actions

Что такое CI/CD?

Простыми словами, CI/CD (Continuous Integration, Continuous Delivery — непрерывная интеграция и доставка) — это технология автоматизации тестирования и доставки новых модулей разрабатываемого проекта заинтересованным сторонам (разработчикам, аналитикам, инженерам качества, конечным пользователям и др.).

Основные процессы

Написание кода. Каждый из разработчиков пишет код своего модуля, проводит ручное тестирование, а затем соединяет результат работы с текущей версией проекта в основной ветке. Для контроля версий используется система Git либо аналогичные решения. Когда участники команды опубликуют код своих модулей в основной ветке, начнется следующий этап.

Сборка

Система контроля версий запускает автоматическую сборку и тестирование проекта. Триггеры для начала сборки настраиваются командой индивидуально: фиксация изменений в основной ветке проекта, сборка по расписанию, по запросу и т. д.

Ручное или автоматическое тестирование.

Когда CI-система успешно проверила работоспособность тестовой версии, код отправляется тестировщикам для ручного обследования.

Релиз

По итогам ручного тестирования сборка получает исправления, а итоговый номер версии кандидата повышается (например, после первого исправления версия v.1.0.0-1 становится v.1.0.0-2).

Развертывание

На этом этапе рабочая версия продукта для клиентов автоматически публикуется на production-серверах разработчика. После этого клиент может взаимодействовать с программой и ознакомиться с ее функционалом как через готовый интерфейс, так и через облачные сервисы.

Поддержка и мониторинг.

Конечные пользователи начинают работать с продуктом. Команда разработки поддерживает его и анализирует пользовательский опыт.

Планирование.

На основе пользовательского опыта формируются запросы на новый функционал для продукта, готовится план доработок. После этого цикл замыкается и переходит в начальную стадию — написание кода. Далее начинается новая итерация CI/CD разработки.

Существующие системы для CI/CD

Рассмотрим системы, участвующие в процессах CI/CD:

- Пишем код: PyCharm, Visual Studio, Eclipse, Vim. - Хранение кода: GitHub, GitLab, BitBucket. - Системы CI/CD: Jenkins, Travis, GitLab CI/CD, Bitbucket Pipelines, GitHub Actions. - Хранение сборок и артефактов: DockerHub, Artifactory, Nexus, GitLab registry. - Среда исполнения (то, где будут развернуты приложения): - Можем запускать нативно на виртуальной машине. - Можем использовать Docker. - Можем ****использовать оркестраторы: Kubernetes, Openshift.

Как мы построим процесс CI/CD?

Как мы видим, систем для каждого нашего процесса огромное количество. Давайте остановимся только на тех, которые потребуются для развертывания нашего приложения.

- Пишем код: любая система на ваш выбор. - Хранение кода: GitHub. - Система CI/CD: GitHub Actions. - Хранение образов: DockerHub. - Среда исполнения: Docker.

GitHub Actions

Платформа для CI/CD от GitHub. По событию (например, git push) запускаются скрипты автоматизации.

Workflows

Workflow — это настраиваемый автоматизированный процесс, который будет выполнять одно или несколько заданий (jobs). Workflows определяются файлом YAML, зарегистрированным в вашем репозитории, и будут запускаться при активации событием в вашем репозитории, или они могут быть запущены как вручную, так и по определенному расписанию.

Events

Event — это определенное действие в репозитории, которое запускает workflow. Например, активность может исходить из GitHub, когда кто-то делает git push, открывает issue и т. д. Также можно запускать workflow по расписанию.

Jobs

Job — это набор шагов (step) в workflow, которые выполняются на одном и том же сервере (runner). Каждый шаг представляет собой либо сценарий командной строки, который будет выполнен, либо действие, которое будет выполнено. Шаги выполняются по порядку и зависят друг от друга. Поскольку каждый шаг выполняется на одном и том же сервере (runner), вы можете обмениваться данными с одного шага на другой. Например, у вас может быть шаг, который создает ваше приложение, за которым следует шаг, проверяющий созданное приложение.

Actions

Actions — это пользовательские библиотеки готовых скриптов. Например, чтобы вручную не писать команды для подключения через SSH, мы можем использовать action appleboy/ssh-action.

Вы можете написать свои собственные actions или найти другие для использования в своих проектах на GitHub Marketplace.

Runner

Runner — это сервер, который запускает ваши workflows при их запуске. Каждый runner может выполнять одно задание одновременно.

Создаем первый workflow

Файлы для GitHub Actions нужно создавать в формате yaml внутри директории `.github/workflows` в проекте. Давайте опишем первый action: скопируем код из репозитория в runner, чтобы далее собрать образ.


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      # Копируем код из репозитория в runner
      - name: clone code 
        uses: actions/checkout@v2
                    

Далее мы можем приступить к сборке Docker-образа.


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      # Копируем код из репозитория в runner
      - name: clone code 
        uses: actions/checkout@v2 
			# Собираем
      - name: docker build
        run: docker build -t my-image .
                    

Переменные окружения

У GitHub Actions есть уже предопределенные переменные окружения, которые помогут нам составлять название версий. Самые важные для нас:

- **GITHUB_RUN_ID** — номер сборки. - **GITHUB_REF_NAME** — название ветки в Git или тега.

Добавляем их в наш файл:


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: docker build
        run: docker build -t sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID .
                    

Далее нам нужно отправить образ в Docker Hub. Для этого мы должны в нем залогиниться. Но мы не хотим писать пароль в репозитории — его все увидят. К счастью, для этого в GitHub есть отличный инструмент — **secrets.**

Секретные данные

Docker Hub Access Tokens

Мы создадим отдельный пароль-токен для наших скриптов. Для этого переходим в Docker Hub в раздел [настройки](https://hub.docker.com/settings/security).

Нажимаем получить токен

Создаем токен, и дальше нам отобразится значение, которое нужно будет сохранить.

Далее создаем в GitHub secrets два секрета:

DOCKERHUB_TOKEN — в значение пишем токен, полученный выше.

DOCKERHUB_TOKEN — в значение пишем токен, полученный выше.

Добавляем шаги с логином и пушем в Docker Hub. Обратите внимание, что sermalenk/flask-app нужно заменить на название своего репозитория.


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: docker build
        run: docker build -t sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID .
      - name: docker login
        run: echo $ff secrets.DOCKERHUB_TOKEN ff | docker login -u $** secrets.DOCKERHUB_USERNAME ** --password-stdin
      - name: docker push
        run: docker push sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID
        прим. вместо ff использовать фиг. скобки 
                    

GitHub Actions CD

Теперь создадим job с развертыванием приложения. У нас будет следующая последовательность действий: - создаем файлы конфигурации: - docker-compose.yaml, - docker_config.py; - загружаем на сервер; - запускаем docker-compose up -d на сервере.

Конфигурация

Как и в случае с логином и паролем от Docker Hub, мы не хотим светить наши секретные данные в этих файлах. Для этого мы воспользуемся командой envsubst — она может подменять переменные окружения, описанные в файлах, на их значения. Это то, что нам нужно.

Создаем job deploy.


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
		...
  deploy:
    runs-on: ubuntu-latest
    needs: build_and_push
    env:
# Здесь мы в секретах гитхаба добавили еще два секрета DB_PASSWORD и DB_NAME
# и пробросили их в переменные окружения
      DB_USER: postgres
      DB_PASSWORD: $ff secrets.DB_PASSWORD ff
      DB_NAME: $ff secrets.DB_NAME ff
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: render configs
        run: |
          mkdir deploy
          cat docker-compose-ci.yaml | envsubst > deploy/docker-compose.yaml
          cat docker_ci_config.py | envsubst > deploy/docker_config.py
          *вместо ff использовать фиг. скобки
                    

Разберем, что произошло в итоге. Мы создали папку deploy. Внутрь нее отрендерили подготовленные файлы docker-compose-ci.yaml и docker_ci_config.py. Вот пример, как это работает:


                        
# cat docker_ci_config.py
SQLALCHEMY_DATABASE_URI = "postgresql://$DB_USER:$DB_PASSWORD@postgres/$DB_NAME"
# cat deploy/docker_config.py
SQLALCHEMY_DATABASE_URI = "postgresql://user:pass@postgres/flask-app"
                    

Загрузка файлов на сервер

Мы будем копировать файлы на сервер через SCP (то есть поверх SSH). Для этого нам потребуется создать отдельного пользователя на нашей виртуальной машине (которую мы купили в первой части предыдущего урока).


                        
sudo su
adduser deploy # создаем сложный пароль, остальные значения можем оставить по умолчанию (просто нажимаем Enter)
usermod -aG sudo deploy # выдаем sudo права пользователю
vim /etc/ssh/sshd_config # редактируем конфигурацию, чтобы разрешить SSH по логину и паролю
# Находим там строчку:
# PasswordAuthentication no
# Меняем на:
# PasswordAuthentication yes
# Сохраняем (:wq)
service ssh reload # перезапускаем демон
                    

Далее мы вписываем имя пользователя, адрес нашей машины и пароль в GitHub secrets (секреты гитхаба) и добавляем новый шаг в нашей job deploy (джоб деплой).


                        
name: Build and deploy action
on: [push]

jobs:
  build_and_push:
		...
  deploy:
    runs-on: ubuntu-latest
    needs: build_and_push
    env:
			# Здесь мы в секретах гитхаба добавили еще два секрета DB_PASSWORD и DB_NAME
      # и пробросили 
      DB_USER: postgres
      DB_PASSWORD: $ff secrets.DB_PASSWORD ff
      DB_NAME: $ff secrets.DB_NAME ff
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: render configs
        run: |
          mkdir deploy
          cat docker-compose-ci.yaml | envsubst > deploy/docker-compose.yaml
          cat docker_ci_config.py | envsubst > deploy/docker_config.py
			- name: clone files to server
        uses: appleboy/scp-action@master
        with:
          # Эти значения надо вписать в секреты гитхаба
          host: $ff secrets.HOST ff
          username: $ff secrets.SSH_USERNAME ff
          password: $ff secrets.SSH_PASSWORD ff
          # Указываем, какие файлы копировать
          source: "deploy/docker-compose.yaml,deploy/docker_config.py"
					# Место на виртуальной машине, куда скопируются файлы
          target: "flask-app"
          strip_components: 1
прим. вместо ff использовать фиг. скобки		
                    

Запуск приложения через GitHub Actions

Тут тоже всё просто. Пользователя мы уже создали, поэтому нам потребуется только добавить новый шаг в job (джоб).


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
		...
  deploy:
    runs-on: ubuntu-latest
    needs: build_and_push
    env:
			# Здесь мы в секретах гитхаба добавили еще два секрета DB_PASSWORD и DB_NAME
      # и пробросили 
      DB_USER: postgres
      DB_PASSWORD: $ff secrets.DB_PASSWORD ff
      DB_NAME: $ff secrets.DB_NAME ff
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: render configs
        run: |
          mkdir deploy
          cat docker-compose-ci.yaml | envsubst > deploy/docker-compose.yaml
          cat docker_ci_config.py | envsubst > deploy/docker_config.py
			- name: clone files to server
        uses: appleboy/scp-action@master
        with:
          # Эти значения надо вписать в секреты гитхаба
          host: $ff secrets.HOST ff
          username: $ff secrets.SSH_USERNAME ff
          password: $ff secrets.SSH_PASSWORD ff
          source: "deploy/docker-compose.yaml,deploy/docker_config.py"
          target: "flask-app"
          strip_components: 1
      - name: run docker-compose
        uses: appleboy/ssh-action@master
        with:
          host: $ff secrets.HOST ff
          username: $ff secrets.SSH_USERNAME ff
          password: $ff secrets.SSH_PASSWORD ff
          # Переходим в директорию и запускаем через sudo команду docker-compose up
          script: |
            cd flask-app
            echo $ff secrets.SSH_PASSWORD ff | sudo -S docker-compose up -d

*прим. вместо ff использовать фиг. скобки
                    

Далее мы можем проверять наше приложение! Для этого откроем адрес, который мы вписывали в secrets.HOST.

Итого, список всех секретов:

Конечный docker-compose-ci.yaml


                        
version: "3.9"
services:
  api:
    image: sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID
    ports:
    - 80:80
    depends_on:
      postgres:
        condition: service_healthy
      migrations:
        condition: service_completed_successfully
    volumes:
      - ./docker_config.py:/code/default_config.py
  migrations:
    image: sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./docker_config.py:/code/default_config.py
    command: flask db upgrade
  postgres:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: $DB_PASSWORD
      POSTGRES_DB: $DB_NAME
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
                    

Конечный файл actions


                        
name: Build and deploy action
on: [push]
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: docker build
        run: docker build -t sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID .
      - name: docker login
        run: echo $ff secrets.DOCKERHUB_TOKEN ff | docker login -u $ff secrets.DOCKERHUB_USERNAME ff --password-stdin
      - name: docker push
        run: docker push sermalenk/flask-app:$GITHUB_REF_NAME-$GITHUB_RUN_ID
  deploy:
    runs-on: ubuntu-latest
    needs: build_and_push
    env:
      DB_USER: postgres
      DB_PASSWORD: $ff secrets.DB_PASSWORD ff
      DB_NAME: $ff secrets.DB_NAME ff
    steps:
      - name: clone code
        uses: actions/checkout@v2
      - name: render configs
        run: |
          mkdir deploy
          cat docker-compose-ci.yaml | envsubst > deploy/docker-compose.yaml
          cat docker_ci_config.py | envsubst > deploy/docker_config.py
      - name: clone files to server
        uses: appleboy/scp-action@master
        with:
          host: $ff secrets.HOST ff
          username: $ff secrets.SSH_USERNAME ff
          password: $ff secrets.SSH_PASSWORD ff
          source: "deploy/docker-compose.yaml,deploy/docker_config.py"
          target: "flask-app"
          strip_components: 1
      - name: run docker-compose
        uses: appleboy/ssh-action@master
        with:
          host: $ff secrets.HOST ff
          username: $ff secrets.SSH_USERNAME ff
          password: $ff secrets.SSH_PASSWORD ff
          script: |
            cd flask-app
            echo $ff secrets.SSH_PASSWORD ff | sudo -S docker-compose up -d

*прим. вместо ff использовать фиг. скобки
                    
© 2023 Все права защищены