CI/CD для проекта в GitHub с развертыванием на AWS EC2

Имеем: проект web API на .net core с исходниками в GitHub.

Хотим: авторазвертывание на виртуалке AWS EC2 после завершения работы с кодом (для примера push в develop ветку).

Инструментарий: GitHub Actions, AWS CodeDeploy, S3, EC2.

Ниже flow процесса, который будем реализовывать, погнали.

Basic CI/CD flowBasic CI/CD flow

1. Пользователи и роли

1.1. Пользователь для доступа к к AWS из GitHub Action

Этот пользователь будет использоваться для подключения к AWS сервисам S3 и CodeDeploy через AWS CLI 2 при запуске GitHub Actions.

1. Заходим в консоль AWS IAM, слева в меню User, и Add User

2. Задаем произвольное имя и ставим галочку Programmatic access

Создание нового пользователяСоздание нового пользователя

3. Далее, в разделе Permissions выбираем Attach existing policies directly и добавляем политики AmazonS3FullAccess и AWSCodeDeployDeployerAccess.

4. Тэги можно пропустить, на этапе Review должно получиться следующее:

Итоговый результатИтоговый результат

5. Жмем Create user и сохраняем данные пользователя у себя. Нам понадобятся Access Key ID и Secret Access Key позднее.

1.2. Сервисная роль для инстансов AWS EC2

Роль необходимо будет навесить на инстансы EC2, на которых будем производить развертывание, чтобы они могли взаимодействовать с сервисом AWS CodeDeploy.

  1. Заходим в консоль AWS IAM, слева в меню Role, и Add Role

  2. Выбираем AWS Service, в Choose a use case выбираем EC2 и переходим далее

  3. Находим и добавляем политику AmazonEC2RoleforAWSCodeDeploy.

  4. Тэги и Review пропускаем

  5. Называем роль, например, ProjectXCodeDeployInstanceRole и на этапе Review должно получиться следующее:

c7e5b851a4b57310fa9db042eef7d6ac.png

1.3. Сервисная роль для AWS CodeDeploy

Роль позволяет сервису AWS CodeDeploy обращаться к инстансам AWS EC2.

1. Заходим в консоль AWS IAM, слева в меню Role, и Add Role

2. Выбираем AWS Service, в Use case выбираем CodeDeploy:

Создание ролиСоздание роли

3. Переходим далее, нужная политика нам уже добавлена автоматом (AWSCodeDeployRole)

4. Называем роль, например, ProjectXCodeDeploy и на этапе Review должно получиться следующее:

Итоговый результат создания ролиИтоговый результат создания роли

2. Инстанс AWS EC2

  1. Разворачиваем подходящий инстанс в AWS EC2

  2. Назначаем ему роль ProjectXCodeDeployInstanceRole, созданную на этапе 1.2

  3. Устанавливаем CodeDeploy Agent на инстанс. Как это сделать смотрим по ссылке.

Важно: Если агента установили на машину до того, как навесили роль, то агента необходимо перезапустить командой sudo service codedeploy-agent restart

3. Конфигурация AWS CodeDeploy

1. Переходим в консоль AWS CodeDeploy

2. Слева в меню кликаем Deploy, внутри Applications

3. Создаем новое приложение кликнув Create application

4. Вводим произвольное имя, например, projectx и выбираем Compute platform EC2/On-Premises

Новое приложениеНовое приложение

5. Далее, провалившись в приложение кликаем Create deployment group

6. Назваем произвольно, develop в нашем случае. Роль выбираем ту, что создале в разделе 1.3 выше (ProjectXCodeDeploy).

7. Deployment type выбираем In place (для примера подойдет).

8. В разделе Environment configuration выбираем Amazon EC2 Instances и далее с помощью тэгов находим подходящие инстансы.

4. Создаем бакет на AWS S3

Для размещения новых сборок нам понадобится бакет в AWS S3.

  1. Заходим в консоль AWS S3, кликаем Create bucket.

  2. Называем, например, projectx-codedeploy-deployments. Убеждаемся что стоит галочка Block all public access. И после кликаем Create bucket.

Создание нового бакета S3Создание нового бакета S3

5. Создаем appspec.yml

Для того, чтобы CodeDeploy Agent понимал как работать с нашим приложением при получении новой сборки нужно ему об этом рассказать. В AWS CodeDeploy для этого нужен специальный файл в корне сборки под названием appspec.yml. В нашей задачке он выглядит следующим образом:

version: 0.0
os: linux
files:
  - source: /
    destination: /opt/projectx
permissions:
  - object: /opt/projectx
    owner: ubuntu
    group: ubuntu
    type:
      - directory
      - file
hooks:
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: ubuntu
  ApplicationStop:
    - location: scripts/stop_server.sh
      timeout: 300
      runas: ubuntu
  1. Создаем appspec.yml version: 0.0 os: linux files:

В двух словах мы сообщаем, где исходники (строка 4), куда их разворачивать (строка 5) и с какими правами (строки 6–12). В разделе хуков мы говорим как поднимать и останавливать приложение. Подробнее по структуре и хукам на события можно почитать в документации.

Важно: Версию надо ставить 0.0, т.к. иначе ругается CodeDeploy и в веб-консоли будет ошибка что версия должна быть именно 0.0 ¯\_(ツ)/¯. Второй момент: при первом деплойменте нужно закомментнтить хук ApplicationStop, иначе на нем будет падать развертывание. Связано это с тем, что агент пытается найти скрипты остановки в прошлом развертывании, которого еще не случилось. Второе и последующие развертывания работают с ApplicationStop корректно.

6. Настраиваем GitHub Actions

Подобрались к самому интересному, настало время описать CI/CD pipeline на базе GitHub Actions.

6.1. Создаем секреты

На странице проекта в GitHub кликаем вкладку Settings, далее в меню слева Secrets и добавляем два секрета:

  • AWS_ACCESS_KEY_ID: значение мы сохранили на шаге 5 раздела 1.1 по созданию пользователь для доступа к AWS

  • AWS_SECRET_ACCESS_KEY: значение мы сохранили на шаге 5 раздела 1.1 по созданию пользователь для доступа к AWS

765fc7d080d403981779879467a7a53b.png

6.2. Настраиваем пайплайн

Создаем в корне приложения директорию .github/workflows. В ней будут размещаться наши файлы с описанием pipeline’ов. Создаем первый, например, develop.yaml. У нас получилось следующее:

name: build-app-action
on: 
  push:
    branches:
      - develop
jobs:
  build:
    name: CI part
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 5.0.101
      - name: Install dependencies
        run: dotnet restore
      - name: Build
        run: dotnet build --configuration Release --no-restore
  
  deploy:
    name: CD part
    runs-on: ubuntu-latest
    strategy:
      matrix:
        app-name: ['projectx']
        s3-bucket: ['projectx-codedeploy-deployments']
        s3-filename: ['develop-aws-codedeploy-${{ github.sha }}']
        deploy-group: ['develop']
    needs: build
    steps:
      - uses: actions/checkout@v2
      # set up .net core
      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 5.0.101
      # restore packages and build
      - name: Install dependencies
        run: dotnet restore
      - name: Build
        run: dotnet build ProjectX --configuration Release --no-restore -o ./bin/app
      # copying appspec file
      - name: Copying appspec.yml
        run: cp appspec.yml ./bin/app
      # copying scripts
      - name: Copying scripts
        run: cp -R ./scripts ./bin/app/scripts
      
      # Install AWS CLI 2
      - name: Install AWS CLI 2
        run: |
          curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
          unzip awscliv2.zip
          sudo ./aws/install
      # Configure AWS credentials
      - 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: ap-south-1
      # Deploy push to S3
      - name: AWS Deploy push
        run: |
          aws deploy push \
          --application-name ${{ matrix.app-name }} \
          --description "Revision of the ${{ matrix.appname }}-${{ github.sha }}" \
          --ignore-hidden-files \
          --s3-location s3://${{ matrix.s3-bucket }}/${{ matrix.s3-filename }}.zip \
          --source ./bin/app
      # Creating deployment via CodeDeploy
      - name: Creating AWS Deployment
        run: |
          aws deploy create-deployment \
          --application-name ${{ matrix.app-name }} \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name ${{ matrix.deploy-group }} \
          --file-exists-behavior OVERWRITE \
          --s3-location bucket=${{ matrix.s3-bucket }},key=${{ matrix.s3-filename }}.zip,bundleType=zip \

Глобально у нас два джоба: сборка и развертывание, причем первое является пререквизитом второго (раздел needs в deploy, строка 31). Срабатывание пайплайна в примере я настроил на push в ветку develop (строки 2–5).

Шаг build

Мы устанавливаем .net, скачиваем нужные пакеты и собираем проект. Именно это и указывается в строках 11–19. Сборку мы проводим на Ubuntu (строки 9 и 23) Здесь же можно прогнать unit-тесты. Если всё случилось, переходим к шагу deploy.

Шаг deploy

Создаем стратегию типа матрицы с параметрами для сборки.

  • app-name: название приложения как завели на шаге 4 раздела 3 по конфигурации CodeDeploy

  • s3-bucket: название бакета, который создали на шаге 2 раздела 4

  • s3-filename: шаблон имени файла для размещения в бакете, используем хэш коммита

  • deploy-group: название группы развертывания как завели на шаге 6 раздела 3.

Первые два шага аналогичны build: ставим .net, качаем пакеты, собираем приложение (тут точечно указан проект, т.к., например, тесты нам не нужны в артефакте, который собираемся катить) и складываем его в отдельную папку (./bin/app в нашем случае, строка 48). Копируем в папку с собранным приложением файл appspec.yml и скрипты запуска и остановки приложения (строки 43–48). Далее со строки 51 мы используем AWS CLI v2, который есть в виде готового action в маркетплейсе GitHub. Устанавливаем AWS CLI2, используем ключи от учетной записи из секретов GitHub проекта, которые настроили чуть выше в разделе 6.1, и указываем регион AWS. Загружаем в AWS S3 собранный проект. Создаем новое развертывание в AWS CodeDeploy. На этом заканчивается работа GitHub Actions.

После этого уже AWS CodeDeploy уведомит EC2 инстансы, которые были в нем настроены, о наличии новой сборки, CodeDeploy Agent на них сходит в AWS S3 за новой версией и развернет её. Понаблюдать за этим можно уже из консоли AWS CodeDeploy.

Резюмируем

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

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

© Habrahabr.ru