В этой статье мы узнаем, что означает deploying приложения в продакшн и как сделать этот процесс автоматическим, рассмотрим Docker и GitHub Actions для этого. Поскольку мы используем Docker, я не буду тратить слишком много времени на то, какие технологии использовались для написания приложения, а сосредоточусь на том, что делать с Docker-изображением. Предположение здесь означает простой сервер, пусть это будет простой VPS (Виртуальный частный сервер), выделенный сервер или любой сервер, где у вас есть SSH для использования. Мы должны выполнить действия, подготовить сервер для получения и выполнения приложения, и настроить pipeline deployments. В следующей части статьи я буду считать, что у вас есть сервер Ubuntu
Давайте подготовим сервер
Первое, что нужно сделать на сервере, это выполнить однократную настройку, цель которой – подготовить сервер для следующих шагов. Для этой цели нам нужно рассмотреть следующие темы:
Docker Compose для запуска приложения и его зависимостей
Docker Compose — это инструмент, который запускает множество приложений Docker в качестве служб и позволяет им общаться друг с другом, а также обладает другими функциями, такими как тома для хранения файлов. Давайте сначала установим Docker и Docker Compose на сервер; вы можете следовать официальному руководству по установке здесь: docs.docker.com/engine/install/ubuntu. После этого важно запустить его от имени пользователя, отличного от root; вот что говорит официальная документация: docs.docker.com/engine/security/rootless.
Настройте учетные данные AWS ECR & CLI.
Заметим, что вы можете достичь той же цели с использованием другого реестра, если хотите, я просто использую AWS, так как это проще для меня.
Поскольку мы используем Docker, изображения должны храниться и извлекаться откуда-то. Для этой цели я использую AWS ECR (Amazon Web Services Elastic Container Registry). Это Docker-реестр в аккаунте AWS. Он дешев в использовании и легко настраивается. Вы также можете использовать Docker Hub для создания私家ного репозитория для ваших изображений. Всё начинается с создания私家ного реестра ECR в аккаунте AWS. Вы нажмёте на “Создать репозиторий” и впишете имя репозитория.
После создания репозитория вы можете скопировать URI репозитория и сохранить его на будущее. У него следующий формат AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/
REPOSITORY_NAME
.
Вам также потребуется настроить учетные данные AWS IAM, которые имеют права на извлечение/推送 в этот репозиторий. Перейдите в сервис IAM, нажмите на нового пользователя и прикрепите к нему следующую политику: AmazonEC2ContainerRegistryFullAccess
, вам не нужно предоставлять ему доступ к консоли AWS. В конце этого процесса вы получите 2 ключа от AWS, секретный ключ
и идентификатор секретного ключа
, сохраните их в сторон, они нам понадобятся дляfuture работы.
Вернувшись на наш сервер, нам нужно установить AWS CLI. Официальный способ установки доступен здесь. https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions. После этого вы можете проверить установку, запустив команду aws --version
. На этом этапе вам нужно будет выполнить команду aws configure
и ответить на вопросы, предоставив предыдущие ключи, созданные в AWS, секретный ключ
и идентификатор секретного ключа
. Вас также спросят выбрать формат вывода, просто JSON, и указать defaultCenter, лучше выбрать регион, где вы создали реестр ECR ранее.
Настройте сценарий запуска приложения.
В了我的м рабочем процессе я написал небольшой скрипт shell, который выполняет определенные действия, это ключевой элемент этого процесса, он входит в реестр, загружает изображение и перезапускает соответствующую docker-сервис, я просто называю его redeploy.sh
и сохраняю его в папке, из которой я хочу запустить мое приложение, вот его содержимое:
#!/bin/bash
# Retrieve AWS ECR login command
aws ecr get-login-password --region [SWS_REGION] | docker login --username AWS --password-stdin [AWS_REGION].dkr.ecr.us-west-2.amazonaws.com
# Associating repositories with identifiers
declare -A repositories=(
["web"]="[REGISTRY_NAME]:latest"
)
# Check if service identifier is provided as a command line argument
if [ -z "$1" ]; then
echo "Please provide a service identifier as a command line argument."
exit 1
fi
service_identifier=$1
# Check if the provided service identifier exists in the repositories array
if [ -z "${repositories[$service_identifier]}" ]; then
echo "Invalid service identifier. Available identifiers: ${!repositories[@]}"
exit 1
fi
# pull the new image from the registry
repository=${repositories[$service_identifier]}
echo "Pulling [AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/$repository"
docker pull "[AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/$repository"
# Change directory to [APP_FOLDER]
cd /home/ubuntu/[APP_FOLDER] || {
echo "Failed to change directory to /home/ubuntu/[APP_FOLDER]"
exit 1
}
# stop and restart the service, this wil force docker compose to redownload the lates image
echo "Re-running service $service_identifier"
docker compose stop "$service_identifier"
docker compose up --no-deps "$service_identifier" -d
# Remove old and un-used docker images
echo "Removing unused Docker images"
docker image prune -fa
echo "Removed Dangling Images"
Первый шаг этого скрипта заключается в входе в аккаунт AWS с помощью AWS CLI, чтобы получить токен, который docker будет использовать при получении docker-изображения, помните, что реестр частный, мы не можем просто забрать его, не пострадав.
Затем мы объявляем список репозиториев и связываем их с каким-то идентификатором,指定的 идентификатор будет использоваться в качестве аргумента командной строки, об этом подробнее позже. После этого мы проверяем, предоставил ли пользователь аргумент, соответствующий существующему идентификатору услуги, мы хотим, чтобы он набрал что-то вроде ./redeploy web
, например, скрипт свяжет аргумент web
с репозиторием web
, как на第二步.
После получения идентификатора услуги мы создаем URL репозитория динамически и выполняем docker pull с ним. Это обеспечивает загрузку docker-изображения на нашу систему.
Теперь скрипт перейдет в папку приложения, /home/ubuntu/[APP_FOLDER]
, это предполагает, что вы все запускаете под пользователем ubuntu
и что его папка HOME
называется ubuntu
, APP_FOLDER
содержит весь набор.
Следующий шаг заключается в остановке и запуске услуги, после чего мы просто удаляем старые иunused изображения командой docker image prune -fa
, вы можете узнать больше здесь: https://docs.docker.com/reference/cli/docker/system/prune/.
Docker compose файл
Compose — это утилита, которая запускает нашу целую систему, ей needed файл с именем docker-compose.yml
, где вы будете определять все, предположим, наше приложение нуждается в услугах redis
и postgres
для работы, вот как это будет выглядеть:
version: '3.9'
services:
web:
image: "[AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/myapp:latest"
ports:
- 8080:8080
depends_on:
- redis
- db
env_file:
- .env
redis:
image: 'redis:alpine'
ports:
- '6379:6379'
db:
image: 'postgres:14'
restart: always
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
PGDATA: /var/lib/postgresql/data/pgdata
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres" ]
timeout: 45s
interval: 10s
retries: 10
ports:
- '5437:5432'
volumes:
- ./opt/postgres/data:/var/lib/postgresql/data
Ваш том ./opt/postgres/data:/var/lib/postgresql/data
будет отображать содержимое сервера Postgres на локальный диск, чтобы оно не было утеряно приостановке работы контейнера Docker. Узнайте больше о запуске Postgres с помощью docker-compose здесь https://medium.com/@agusmahari/docker-how-to-install-postgresql-using-docker-compose-d646c793f216. Я использовал директиву env_file
, которая позволяет docker-compose читать файл и загружать его содержимое в контейнер Docker во время выполнения, я сделал это, потому что обычно файл docker-compose добавляется в VCS, и я не хочу хранить переменные окружения в нем напрямую через директиву environment
в службе. Обратите внимание, что наша служба здесь называется web
, ранее мы написали файл redeploy.sh
и intendirium его запустить следующим образом:
./redeploy.sh web
Аргумент web
связан с именем нашей службы, этот файл просто отображает аргумент на имя службы в docker-файле.
Настройте.service Linux, чтобы все работало
На этом этапе мы должны создать.service Linux, которая будет обеспечивать запуск приложения каждый раз, когда запускается сервер или наше приложение останавливается. Следующий скрипт поможет вам это сделать:
[Unit]
Description=[APP_NAME] service executed by docker compose
PartOf=docker.service
After=docker.service
After=network.target
[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/home/ubuntu/[APP_FOLDER]
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
Давайте проанализируем это !!!
-
Раздел
Unit
описывает нашу службу и specifies, частью какой службы является наш модуль, в данном случае это служба docker, это обеспечит работу нашей службы всегда, когда работает служба docker. -
Раздел
Service
описывает, как запустить наш сервис, интересные части этоWorkingDirectory
,ExecStart
иExecStop
команды, они будут использоваться в соответствии с их названием, например, если сервис называетсяmyapp
, то при вводе командыsystemctl start myapp
будет выполнена командаExecStart
. Вы можете узнать больше о службах Linux здесь https://www.redhat.com/sysadmin/systemd-oneshot-service. Подробнее о том, как запустить сервис docker с помощьюsystemd
, вы можете узнать здесь: https://bootvar.com/systemd-service-for-docker-compose/
Этот сервис должен быть установлен таким образом, чтобы система запускала его по мере необходимости, вам придется сохранить его в файле с именем, например: myapp.service
touch myapp.service
# open it
nano myapp.service
# paste the previous scrip in it
cp myapp.service /etc/systemd/system/myapp.serivce
На этом этапе он признан Linux сервисом, вы можете запустить systemctl start myapp
, чтобы его запустить. Следующая необходимая команда:
systemctl enable myapp.service
Это обеспечит автоматический запуск сервиса сервером при каждом перезагрузке. Вы можете узнать больше здесь: https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6
Веб-сервер
Я использовал Nginx для этой задачи, он мал и мощен, широко используется и может действовать в качестве балансировщика нагрузки, сервера статических файлов,反向 прокси и многого другого. Первое, что нужно сделать, это установить его.
sudo apt-get install nginx
На этом этапе docker-изображение якобы работает, предположим, оно содержит приложение, работающее на порту 8080
, и этот порт привязан к серверу через файл docker-compose. Нам нужно настроить конфигурацию обратного прокси между Nginx и нашим портом. Вот необходимая конфигурация:
upstream app_backend {
server localhost:8080; # the appliction port
}
server {
listen 80;
server_name [DOMAIN_NAME];
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
позвольте назвать эту конфигурацию myapp.conf
и сохранить ее в папке, где nginx ее найдет, эта папка среди других называется /etc/nginx/conf.d/
.
sudo touch /etc/nginx/conf.d/myapp.conf
sudo nano /etc/nginx/conf.d/myapp.conf
# paste the content there
Теперь все, что нам нужно, это протестировать и перезапустить сервис NGINX с помощью следующих команд
sudo nginx -t # test if the config is valid
sudo nginx -s reload # reload the nginx service so it will consider it
Эта конфигурация instruct nginx listened to the traffic on the port 80
and with the domain name [DOMAIN_NAME]
and send it to your app server on the port 8080
via the directive proxy_pass
, строка location / {
просто означает перехват всех запросов, начинающихся с /
, и выполнение действий, записанных под блоком location
. Узнайте больше здесь https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-to-setup-Nginx-reverse-proxy-servers-by-example.
Пайплайн сборки
После настройки сервера нам нужно настроить конвейер сборки, который состоит в основном из одного шага – написать файл конвейера Github Action и добавить его в проект, приступаем.
GitHub Actions Setup
GitHub action будет использоваться для сборки образа докера из нашего исходного кода и заталкиваться в реестр, откуда образ будет извлекаться и выполняться на сервере. Я возьму пример Dockerfile для этого примера, но на практике вам придется написать свой собственный Dockerfile. Для приложения express.js файл docker будет выглядеть так:
# Fetching the minified node image on apline linux
FROM node:slim
# Declaring env
ENV NODE_ENV production
# Setting up the work directory
WORKDIR /express-docker
# Copying all the files in our project
COPY . .
# Installing dependencies
RUN npm install
# Installing pm2 globally
RUN npm install pm2 -g
# Exposing server port
EXPOSE 8080
# Starting our application
CMD pm2 start process.yml && tail -f /dev/null
Создание и запуск этого файла docker запустит наше приложение на порту 8000, но в нашем случае мы должны запустить его с помощью docker-compose.
Далее нужно настроить конвейер действий GitHub. Для этого просто создайте папку .github/workflows
в корне проекта и создайте файл с именем docker-build.yml
, в него мы запишем наш конвейер.
name: Build, Push to ECS and Deploy to Server
on:
push:
branches: ['deploy/main']
jobs:
build:
name: Build Web Image
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: [AWS_REGION]
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: [REPOSITORY_NAME]
IMAGE_TAG: latest
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Restart the service via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
port: ${{ secrets.PORT }}
script: /home/ubntu/[APP_DIRECTORY]/redeploy.sh web
Здесь нужно выполнить несколько шагов:
-
Конфигурировать учетные данные AWS
: здесь система загрузит предыдущий aws-ключ, который вы создали ранее, вам нужно будет зарегистрировать его в секретах вашего аккаунта GitHub.
-
Build, tag, and push image to Amazon ECR
: на этом шаге будет выполнена командаdocker build
иdocker push
для создания образа docker.
-
Перезапуск услуги через SSH
этот шаг подключится к серверу и перезапустит все приложение сразу.
Эта конвейерная линия будет запускаться каждый раз, когда.merge-запрос будет объединен с веткой deploy/main
.
on:
push:
branches: ['deploy/main']
На этом этапе вся система настроена и связана, теперь можно редактировать и применять её к вашему конкретному случаю. В следующей статье я поделюсь процессом создания приложения для производства и запуска в docker файле.
Заключение
Эта статья пытается описать процесс, который я использую для настройки VPS для автоматизации при deployment. Она описывает, как настроить процесс выполнения приложения внутри сервера и процесс создания приложения, каждую часть можно выполнить с помощью другого инструмента, например, вы можете заменить nginx на Treafik, если хотите, и можете заменить сервис systemd
на программу в supervisor
и более. Этот процесс не охватывает дополнительные вещи, такие как резервное копирование сервера или закрытие defaultCenter портов на серверах, об этом будет рассказано в будущих статьях. Не стесняйтесь задавать вопрос, если вы хотите адаптировать это к вашему потоку. В другой статье я сосредоточусь на том, как настроить приложение для production-ready в плане deployment, это та часть процесса, которая предшествует созданию Docker Image.
Надеюсь, вам понравилось чтение.
Я здесь, чтобы помочь вамImplementировать это в вашей компании или команде, чтобы вы могли сосредоточиться на своих основных задачах и сэкономить деньги перед тем, как начать использовать огромный потенциал Mastodon.