Використання AWS та GitHub Actions застосунку дозволяє автоматизувати та спрощувати процес розгортання, забезпечує швидкість та ефективність розробки, а також надає можливості масштабування та інтеграції з сервісами AWS. Докладно про це розповів Front-end Team Lead у Boosters Олександр Барило на мітапі у генезійському фронтенд-ком'юніті. Він уже шість років займається розробкою й пів року очолює команду фронтенд-розробників. Публікуємо найважливіше з виступу.
Про які AWS ресурси піде мова
Розглянемо їх детальніше
Amazon CloudFront — це вебсервіс, який пришвидшує розповсюдження статичного та динамічного вебконтенту, як-от .html, .css, .js, а також файли зображень, серед користувачів. CloudFront доставляє контент через мережу дата-центрів, які називаються edge locations. CloudFront дублює вихідні ресурси на локацію, яка ближча до юзера — так він швидше отримуватиме потрібний контент.
Amazon S3 Bucket — це контейнер для об’єктів, що зберігаються в Amazon S3. Bucket дає змогу зберігати будь-яку кількість об’єктів. Всього в акаунті можна мати до сотні подібних Bucket.
Amazon Route 53 — це високодоступний масштабований хмарний вебсервіс системи доменних імен (DNS). Дає змогу перенаправляти кінцевих користувачів до інтернет-застосунків завдяки перетворенню доменних імен (наприклад, www.example.com) у формат цифрових IP-адрес (наприклад, 192.0.2.1), які використовуються комп’ютерами. З його допомогою потрібно буде перенаправити юзерів до вашого домену.
Як це виглядає у вебінтерфейсі
Йдемо у Console Home, створюємо акаунт, підв'язуємо свою картку. Тепер можна щось створювати натискаємо на S3. Там знаходяться Buckets, нам потрібно створити новий.
Для управління записами є Route 53. Натискаємо на нього, потім переходимо до Hosted Zone. Якщо у вас чистий акаунт, то Hosted Zone не буде, але в моєму прикладі вона є. Тут міститься ряд записів. Якщо потрібно створити новий — така опція також є.
Недолік цього способу полягає у тому, що є ризик забути порядок дій, а також у тому, що інші користувачі мають можливість внести зміни, які буде важко відстежити.
З цим допомагає впоратися AWS CDK — платформа розробки програмного забезпечення з відкритим вихідним кодом, яка дає змогу визначати ресурси для хмарних застосунків, використовуючи звичні мови програмування. Це означає, що код, який ви написали, під час запуску буде сетапити ті чи інші ресурси.
AWS CDK підтримує декілька мов, як-от TypeScript, JavaScript, Python, Java, C, .Net, Go, однак тут і далі ми будемо використовувати TypeScript.
Підготовка до роботи: що потрібно
Для того, щоби почати роботу, знадобляться:
Тож, зробимо тестовий CDK-проєкт — звичайний creator app застосунок, який у нас буде лежати у своїй папці. Потрібно виконати команду:
cdk init app — language typescript
Для того, щоб можна було деплоїти через CDK, вам потрібно створити credentials для юзера. Це робиться через окремий сервіс AWS, який називається IAM.
У ньому відображається список юзерів.
Знаходите вкладку security credentials, а там — колонку Access Key ID. Спочатку у вас їх не буде: credentials потрібно генерувати, а потім зберігати для подальшого використання.
Використовуючи їх у файлі AWS Credentials, можна засетапити юзера по дефолту або з певним іменем. Тоді для нього прописується Acceess Key та Secret Access Key. Далі покажу, де це буде застосовуватися.
CDK project
Перевага CDK — все, що ви створите, можна буде потім почистити за допомогою команди destroy. На початку проєкт має ось такий вигляд:
Спершу акаунт потрібно хардкодити в конфіг.
env: { account: ‘aws-account’, region: ‘us-east-1’ }
Після того, як ви це зробили, вам потрібно буде запустити команду для Bootstrap.
export CDK_NEW_BOOTSTRAP=1
cdk --profile=test_acount_cdk_user bootstrap
Тоді CDK під’єднається до вашого акаунту, піде в S3 й створить свій Bucket для зберігання артефактів, які йому необхідні для порівняння змін, щоб зрозуміти, що потрібно оновити, а що видалити.
Далі приступаємо до конфіга.
cdk deploy --profile test_acount_cdk_user
Це команда для того, щоб деплоїти, але зараз мова про те, як сконфігурувати ваш stack.
Перше, що ми створимо — це Bucket. Більшість ресурсів зашиті до core-бібліотеки, aws-cdk-lib. Якщо ні — потрібно буде шукати та встановлювати додаткові бібліотеки. У попередніх версіях усі ресурси були розкидані окремими бібліотеками, але поступово їх додають уже в core.
Спочатку створюємо S3 Bucket, передаємо йому наш stack.
Звертаємо увагу на
// s3 bucket
const frontendAppBucket = new Bucket(this, 'Test-S3Bucket', {
bucketName: 'test-v1-s3-bucket'
})
— це ідентифікатор, який має бути унікальним на рівні всього stack. Всередині є конфіг для Bucket, і потім потрібно буде передати тільки bucket name. Коли ми це запустимо, цей шматок коду створить нам ось такий Bucket.
Створюємо нову Hosted Zone, яка створить необхідні записи в Route 53.
// hosted-zone
const hostedZone = new HostedZone(this, 'MyHostedZone', {
zoneName: domainName
})
Коли ми запускаємо команду для деплоя, вона створює HostedZone, яка містить ось такі записи.
Тоді нам потрібно «піти» у реєстратор, де ми створювали домен, й прописати все це у відповідному вікні.
Команда
// sertificate
const appCert = generateCertificate(this, domainName, hostedZone)
генерує сертифікат. Тут використовується метод from.dns, вона сама пройде валідацію за допомогою запису в RoutedZone.
S3 bucket, CloudFront, Deploy User
Отже, Bucket ми створили. Тепер потрібно створити CloudFront Distribution
new CloudFrontWebDistribution()
Це те, що називається cdn. Для CloudFront Distribution створюється користувач, якому надається доступ до S3 bucket.
// Cloudfront user
const cfoai = new OriginAccessIdentity(
this,
'CloudFrontOriginAccessIdentity',
{
comment:
'Cloudfront user which will be allowed to access the site s3 bucket'
}
)
// add access cloud-front-user to S3 bucket
frontendAppBucket.addToResourcePolicy(
new PolicyStatement({
principals: [
new CanonicalUserPrincipal(
cfoai.cloudFrontOriginAccessIdentityS3CanonicalUserId
)
],
actions: ['s3:List*', 's3:Get*'],
resources: [`${frontendAppBucket.bucketArn}/*`]
})
)
Це для того, щоб з S3 Bucket направити на цей CloudFront. Юзера ми також створюємо для того, щоб деплоїти. Відповідно, коли ми будемо заливати це в S3, нам знадобляться credentials. Далі цьому юзеру передаємо permissions на ReadWrite для bucket.
// grand permissions for S3 bucket
frontendAppBucket.grantReadWrite(deployUser)
Потім створили CloudFront, додали до deployUser політику за допомогою якої він може інвалідувати цей CloudFront. Нижче додали рекорд, який буде перенаправляти дані з нашої доменної зони на інстанс CloudFront.
deployUser.addToPolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'cloudfront:GetInvalidation',
'cloudfront:CreateInvalidation',
],
resources: [
`arn:aws:cloudfront::${process?.env?.account}:distribution/${cf.distributionId}`,
],
})
);
new ARecord(this, 'AppDNS', {
recordName: domainName,
zone: hostedZone,
target: RecordTarget.fromAlias(new CloudFrontTarget(cf)),
});
Далі йдуть висновки — те, що буде виводитися у консоль, щоби ми могли побачити, що саме створилося.
new CfnOutput(this, 'FrontendAppBucket', {
description: 'Js App Bucket name',
value: frontendAppBucket.bucketName,
exportName: `${appName}AppBucket`,
});
new CfnOutput(this, `${appName}AppCloudFrontDistributionID`, {
description: 'CloudFront Distribution ID',
value: cf.distributionId,
exportName: `${appName}AppCloudFrontDistributionID`,
});
new CfnOutput(this, 'DomainApp', {
description: 'App domain',
value: domainName,
exportName: `${appName}App`,
});
new CfnOutput(this, `${appName}AppCertARN`, {
description: 'App Certificate ARN',
value: appCert.certificateArn,
exportName: `${appName}AppCertARN`,
});
new CfnOutput(this, `${appName}AppDeployUserARN`, {
description: 'Deploy frontend user ARN',
value: deployUser.userArn,
exportName: `${appName}AppDeployUserARN`,
});
new CfnOutput(this, 'DeployFrontendAccessKeyId', { value: accessKey.ref });
new CfnOutput(this, 'DeployFrontendSecretAccessKey', {
value: accessKey.attrSecretAccessKey,
});
}
}
Пробуємо задеплоїти, робимо це за допомогою команди
cdk deploy -- profile test_account_cdk_user
У цей момент на основі нашого конфігу генерується Json-файл, який «дивиться», що змінилося і публікує. В output виводиться інформація щодо створених ресурсів, де ми можемо отримати credentials, які нам знадобляться в GitHub Actions.
Як налаштувати GitHub Actions
Ось приклад команд які запускаються в екшені. Фактично, це послідовність скриптів які виконуються для того, щоб збілдити й задеплоїти проєкт.
- name: Install packages
run: npm install
- name: Run unit tests
run: npm run test:ci
- name: Run linters
run: npm run lint
- name: Build project
run: npm run build
- name: Deploy to S3
uses: jakejarvis/s3-sync-action@master
Для конфігурації екшенів в кореневій папці проєкту потрібно створити папку `.github/workflows` — в ній зберігаються конфіги екшенів. Я створю два конфіги: один для pull requests, а інший — для deploy.
В pull requests спочатку вказується, для яких подій потрібно запускати Actions. Далі йде опис jobs, де потрібно вказати, з якої директорії запускати скрипти (якщо ви прямо з кореня все запускаєте, то це можна пропустити).
name: Build pull request to the master
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the selected branch
on:
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# set default directory
defaults:
run:
working-directory: ./genesis-frontend
# The type of runner that the job will run on
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
Уже на тому етапі, коли ви зробите якийсь pull request master — запуститься останній скрипт.
- name: Install packages
run: npm install
- name: Run unit tests
run: npm run test:ci
- name: Run linters
run: npm run lint
- name: Build project
run: npm run build
Спробуємо внести зміни — додамо новий текст.
Припустімо, що ми хочемо закомітити ці зміни, аби вони потрапили в потрібний домен. Йдемо на GitHub. Створюємо pull request та переходимо на вкладку Actions.
Тут ми можемо подивитися логи, як усе збирається. Це свого роду перевірка, щоб впевнитися, що після змін в коді проходять всі тести, білд і цей код можна сміливо релізити.
Вище ми створювали два конфіги. Раніше ми працювали з pull request, тепер розглянемо конфіг для deploy.
- name: Deploy to S3
uses: jakejarvis/s3-sync-action@master
with:
args: --acl public-read --delete
env:
AWS_S3_BUCKET: ${{ secrets.AWS_PROD_BUCKET_NAME }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PROD_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PROD_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
SOURCE_DIR: "genesis-frontend/build"
- name: Invalidate Cloudfront cache
uses: chetan/invalidate-cloudfront-action@master
env:
DISTRIBUTION: ${{ secrets.PROD_CF_DISTIRIBUTION_ID }}
PATHS: "/*"
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PROD_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PROD_SECRET_ACCESS_KEY }}
Для того, щоб це все працювало, потрібно все що ви отримали з cdk піти і засетапити secrets з відповідними значеннями. Secrets, які використовуються, можна побачити в Actions, а отримати з cdk.
Спробуємо викатити апдейт. Попередня версія коду уже залита. Дивимося у GitHub стан Actions. Все успішно — проєкт зібрався.
Тепер виливаємо наш апдейт на продакшн: мерджимо pull request, confirmation, переходимо до Actions і запускаємо їх заново. Раніше вони називалися pull request master, а тепер — prod build and deploy. Після того, як код збілдився, проєкт задеплоївся в S3 та інвалідувало кеш на Cloud Front.
Було:
Стало:
З таким процесом deploy підводні камені можуть з'являтися на кожному етапі: не вистачає доступів, неправильне налаштування. Однак рішення усіх цих труднощів нескладно нагуглити.
Зручність рішення — його велика перевага, адже весь set up зберігається у вигляді коду на GitHub. Відповідно, якщо хтось внесе зміни, ви завжди можете подивитися GitHub і зрозуміти, що саме перемінилося. Такого не вийде зробити, якщо сетапити інфраструктуру через вебінтерфейс.
У себе в проєкті ми ухвалили рішення: все, що сетапиться на AWS, має сетапитися через cdk — для того, щоб була якась стабільність і ми не втрачали потрібні налаштування.