Легко и быстро: автоматизация развертывания AWS EC2 с GitHub Actions и Docker Hub

Введение

Автоматизируя процесс развертывания изменений кода в экземпляре Amazon Web Services (AWS) Elastic Compute Cloud (EC2) с помощью Github Actions и Docker Hub, разработчик в итоге экономит время и силы.

В этом руководстве мы пошагово настроим экземпляр AWS EC2, создадим и добавим в DockerHub образ Docker и настроим рабочий поток Github Actions для автоматизации процесса развертывания. В итоге научимся оптимизировать рабочий процесс развертывания, уменьшая риск человеческой ошибки.


Необходимые условия

1. Код для сборки

Он нужен для создания образа Docker  —  легковесного, автономного, исполняемого пакета, в котором содержится все необходимое для запуска приложения, в том числе код, библиотеки и зависимости.

2. Экземпляр AWS EC2 с файлом .pem и установленным Docker

В AWS EC2 развертывается приложение. Используем экземпляр Ubuntu AWS EC2, хотя это не обязательно должен быть Ubuntu. Настраивается AWS EC2 согласно официальной документации AWS. Файл .pem связан с экземпляром для SSH-доступа. Docker устанавливается в экземпляре AWS EC2 согласно официальной документации Docker.

3. Учетная запись Docker Hub с токеном доступа

Docker Hub  —  это облачная служба реестра для хранения и обмена образами Docker. Учетную запись для их размещения в ней создаем здесь. Затем, следуя инструкциям официальной документации Docker, генерируем токен доступа для аутентификации в рабочем потоке Github Actions через Docker Hub.

Приступим к автоматизации развертывания AWS EC2.


Создание Dockerfile

Чтобы сделать Docker-образ приложения, создадим dockerfile, в котором описывается сборка образа.

  1. В корневом каталоге проекта с помощью текстового редактора создаем файл dockerfile.
  2. В dockerfile начинаем с указания базового образа, например для приложения на Go это официальный Docker-образ Golang:
##указываем базовый образ
FROM golang:1.19-alpine

Этой строкой за основу Docker-образа берем образ golang:1.19-alpine.

3. Затем в Docker-образе указываем рабочий каталог для приложения:

##создаем папку APP
RUN mkdir /app
##задаем главный каталог
WORKDIR /app

Этими строками создаем в Docker-образе каталог app и делаем его рабочим.

4. Копируем остальную часть кода приложения в Docker-образ:

##копируем весь файл в приложение
ADD . /app

Этой строкой в каталог /app Docker-образа копируется остальная часть кода приложения.

5. Создаем исполняемый файл для приложения:

##создаем исполняемый файл
RUN go build -o main .

Чтобы создать для приложения исполняемый файл main, этой строкой в Docker-образе запускается команда go build.

6. Указываем команду, выполняемую при запуске контейнера Docker:

##запускаем исполняемый файл
CMD ["/app/main"]

Этой строкой при запуске контейнера Docker запускается исполняемый файл /app/main.

Вот готовый Dockerfile:

##в качестве базового используем официальный Docker-образ Golang
FROM golang:1.19-alpine

##создаем папку APP
RUN mkdir /app

##задаем главный каталог
WORKDIR /app

##копируем весь файл в приложение
ADD . /app

##создаем исполняемый файл
RUN go build -o main .

##запускаем исполняемый файл
CMD ["/app/main"]

Создадим Docker-образ и отправим его в Docker Hub.


Настройка рабочего процесса GitHub

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

Чтобы настроить рабочий процесс GitHub, создадим в каталоге репозитория .github/workflows файл YAML, например main.yml. В этом файле определяются запускаемые задания и выполняемые экшены.

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

Вот пример простого YAML-файла для сборки и отправки Docker-образа в Docker Hub, когда в ветке main появляется новый push:

# Запускается рабочий процесс, когда в ветке main появляется push
on:
  push:
    branches:
      - main

jobs:
  build:
    # На компьютере с Ubuntu запускается задание
    name: Build
    runs-on: ubuntu-latest    
    
    steps:
      # Извлекаем код из репозитория
      - name: Checkout  
        uses: actions/checkout@v3

      # Входим в Docker Hub, используя secrets
      - name: Login to Docker Hub  
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USER }}  
          password: ${{ secrets.DOCKER_TOKEN }}
          
      # Настраиваем Docker Buildx
      - name: Set up Docker Buildx  
        uses: docker/setup-buildx-action@v2

      # Создаем Docker-образ и отправляем его в Docker Hub
      - name: Build and push  
        uses: docker/build-push-action@v3
        with:
          context: .  
          file: ./Dockerfile  
          push: true  
          tags: ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}:latest

      # Запускаем удаленные команды в экземпляре EC2 через SSH
      - name: executing remote ssh commands using password  
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.EC2_HOST }}  
          username: ${{ secrets.EC2_USERNAME }}  
          key: ${{ secrets.EC2_KEY }} 
          port: ${{ secrets.EC2_PORT }}

          # Удаленные команды, выполняемые в экземпляре EC2
          script: | 
            touch deploy.txt
            echo "Great, it works! at $(TZ='Asia/Jakarta' date "+%A %d-%b-%Y %H:%M:%S %Z")" >> deploy.txt
            sudo chmod 777 /var/run/docker.sock
            docker pull ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}:latest 
            docker stop ${{ secrets.CONTAINER }}
            docker rm ${{ secrets.CONTAINER }} 
            docker rmi ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }} 
            docker run --name ${{ secrets.CONTAINER }} -d -p 80:8000 ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}

Разбор рабочего процесса GitHub

on:
push:
branches:
- main

Здесь определяется событие, которым запускается рабочий процесс. В данном случае это появление нового push в ветке main.

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:

Здесь определяется задание build, запускаемое в рабочем процессе на последней версии Ubuntu.

- name: Checkout
uses: actions/checkout@v3

На этом шаге код извлекается из репозитория в runner.

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}

На этом шаге входим в Docker Hub, вводя имя пользователя и токен, сохраненный в настройках репозитория как secrets.

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

На этом шаге настраивается CLI-плагин Docker Buildx, которым расширяются возможности интерфейса командной строки Docker.

- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}:latest

На этом шаге с помощью Dockerfile в текущем каталоге создается Docker-образ и по указанным тегам отправляется в Docker Hub.

# Запускаем удаленные команды в экземпляре EC2 через SSH
- name: executing remote ssh commands using password  
  uses: appleboy/[email protected]
  with:
    host: ${{ secrets.EC2_HOST }}  
    username: ${{ secrets.EC2_USERNAME }}  
    key: ${{ secrets.EC2_KEY }} 
    port: ${{ secrets.EC2_PORT }}

# Удаленные команды, выполняемые в экземпляре EC2
    script: | 
      touch deploy.txt
      echo "Great, it works! at $(TZ='Asia/Jakarta' date "+%A %d-%b-%Y %H:%M:%S %Z")" >> deploy.txt
      sudo chmod 777 /var/run/docker.sock
      docker pull ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}:latest 
      docker stop ${{ secrets.CONTAINER }}
      docker rm ${{ secrets.CONTAINER }} 
      docker rmi ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }} 
      docker run --name ${{ secrets.CONTAINER }} -d -p 80:8000 ${{ secrets.DOCKER_USER }}/${{ secrets.IMAGE }}
  • В этом блоке кода используется экшен appleboy/ssh-action для выполнения удаленной команды SSH на сервере, указанном в файле secrets. Выполняемый сценарий определяется параметром script.
  • Сценарий начинается с создания нового файла deploy.txt и добавления к нему строки Great, it works! вместе с текущей датой и временем в часовом поясе Азии/Джакарты или вашем.
  • Затем, чтобы изменить разрешения сокет-файла Docker для доступа к нему текущего пользователя, выполняется команда sudo chmod 777 /var/run/docker.sock.
  • Для получения последней версии Docker-образа, указанной в файле secrets, применяется команда docker pull.
  • Команды docker stop, docker rm и docker rmi используются для остановки и удаления любого имеющегося контейнера и образа Docker с тем же именем, что указан в файле secrets.
  • Команда docker run используется для запуска нового контейнера Docker с именем, указанным в файле secrets, и с помощью образа из secrets.
  • Флагом -d в фоновом режиме запускается контейнер как процесс демона.
  • Флагом -p 80:8000 порт 80 хоста сопоставляется с портом 8000 контейнера: получаем доступ к контейнеру по HTTP через порт 80.
  • Через флаг -e задаются переменные окружения для контейнера, например DB Host, DB Password, JWT Key и т. д.

В итоге в этом блоке кода получается последняя версия Docker-образа, останавливаются и удаляются любые имеющиеся контейнеры с одинаковым именем, запускается новый контейнер, а порт контейнера 8000 сопоставляется с портом хоста 80.


Настройка секретных переменных

Чтобы развернуть приложение в AWS EC2, настроим несколько секретных переменных в репозитории GitHub. Эти secrets надежно зашифрованы и хранятся в GitHub, доступ к ним имеют только авторизованные пользователи.

  1. Переходим в репозиторий GitHub.
  2. Нажимаем на вкладку Settings («Настройки»).
  3. Выбираем слева Secrets.
  4. Попадаем на страницу Actions secrets and variables («Секреты и переменные экшенов») репозитория. Здесь создаются, редактируются и удаляются секреты для рабочих процессов GitHub Actions.
Секретные переменные репозитория

Вот список необходимых секретных переменных с кратким описанием.

  • DOCKER_USER: имя пользователя Docker Hub.
  • DOCKER_TOKEN: токен доступа с разрешением отправлять образы в репозиторий Docker Hub.
  • IMAGE: имя Docker-образа.
  • CONTAINER: имя контейнера Docker для запуска приложения.
  • EC2_HOST: IP-адрес или доменное имя экземпляра EC2, например ec2–xx–xxx–xxx–xx.ap-southeast-1.compute.amazonaws.com.
  • EC2_USERNAME: имя пользователя для входа в экземпляр EC2, обычно это ubuntu.
  • EC2_KEY: закрытый ключ для входа в экземпляр EC2.
  • EC2_PORT: SSH-порт для подключения к экземпляру EC2, по умолчанию 22.

Обязательно настройте эти секретные переменные в репозитории GitHub перед запуском рабочего процесса.


Выполнение рабочего процесса

После настройки фиксируем изменения кода в ветке main репозитория: рабочий процесс запустится автоматически.

Чтобы проверить его статус, переходим на вкладку Actions репозитория GitHub. Здесь имеется список всех выполненных рабочих процессов и их статус. Выбрав рабочий процесс, просматриваем информацию о нем, включая любые ошибки и предупреждения:

Неудавшийся запуск рабочего процесса

Если рабочий процесс выполнится, появится сообщение о том, что развертывание завершено. Можно также проверить работоспособность сайта.

Успешный запуск рабочего процесса

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

Логи deploy.txt
Развернутый образ
Развернутый контейнер

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


Заключение

В этом руководстве мы пошагово настроили базовый рабочий процесс GitHub для развертывания контейнеризированного веб-приложения. С мощью GitHub Actions процесс развертывания приложения автоматизируется, а время высвобождается на другие важные задачи. Надеемся, статья была полезной для понимания основ GitHub Actions, Docker, Docker Hub и того, как применять их вместе для упрощения рабочего процесса.

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Muhammad Habibullah: Easy and Fast : Automating AWS EC2 Deployment with GitHub Actions and Docker Hub

Предыдущая статьяУтилитные классы в Kotlin с точки зрения Java-разработчика
Следующая статьяПолное руководство по “this” в JavaScript