はじめに
また3ヶ月以上もブログを書いていなかったらしい。(生存ブログに関しては11月20日から書いていない)
今回は、仕事で AWS 環境化においての Batch 、 ECR 及び CloudWatch Events の更新を手動でやるのはダルいなという判断(脳内会議)になったので最近熱いと噂の GitHub Actions
を使って CD 環境を作ろうとなって実装したのでその記事
CircleCI 等の環境でも同じようなことはできますが、僕が CircleCI を別のリポジトリで使いすぎて週間クレジットをリアルに毎週使い切るということになりそうなのでそれはそっちで使うということで使わないという判断です。
AWS のサービスの CodeBuild を使うという手もあるんですが、今の仕事では CloudFormation
(以下、見出し等以外 CFn
) を使って一貫管理してないところもある *1 のですが95%以上は管理されているのでそのためにユーザ (Role
) 増やして CodeBuild のログを見られるように修正パッチを毎回デプロイするとかかが面倒なので GitHub Actions での対応を判断しました。
環境
CloudFormation
Batch 内で RDS との通信等がありますが今回はスルーします。
またサブネットは外部との通信ができるように Public IPv4 を起動に設定するようにしてます。 *2
再度、書き起こして作ったものなのでミスがありそうですがごめんなさい。
Batch 関連
Parameters
KeyName: Type: AWS::EC2::KeyPair::KeyName Description: 接続するためのキー名
IAM
Batch で使用する権限の作成
BatchServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - batch.amazonaws.com - events.amazonaws.com - ecs-tasks.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole - arn:aws:iam::aws:policy/service-role/AWSBatchServiceEventTargetRole Path: /service-role/ RoleName: BatchServiceRole ECSInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: ec2.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonECS_FullAccess - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - arn:aws:iam::aws:policy/AmazonEC2FullAccess - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role Path: / RoleName: ECSInstanceRole ECSInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: ECSInstanceProfile Rules: - !Ref 'ECSInstanceRole'
ECR
言わずもがなリポジトリ
Repository: Type: AWS::ECR::Repository Properties: RepositoryName: Repository RepositoryPolicyText: Statement: - Action: - ecr:GetDownloadUrlForLayer - ecr:BatchGetImage - ecr:BatchCheckLayerAvailability Effect: Allow Principal: AWS: !GetAtt 'ECSInstanceProfile.Arn'
Batch
Batch の定義等の作成
ComputeEnvironment: Type: AWS::Batch::ComputeEnvironment Properties: ComputeResources: DesiredvCpus: 1 Ec2KeyPair: !Ref 'KeyName' InstanceRole: !GetAtt 'ECSInstanceProfile.Arn' InstanceTypes: - optimal MaxvCpus: 64 MinvCpus: 0 SecurityGroupIds: - !Ref 'BatchSecurityGroup' Subnets: - !Ref 'Subnet' Type: EC2 ServiceRole: !GetAttr 'BatchServiceRole.Arn' State: ENABLED JobDefinition: Type: AWS::Batch::JobDefinition Properties: ContainerProperties: Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}:latest' Memory: 2048 Vcpus: 2 JobDefinitionName: JobDefinition Type: container JobQueue: Type: AWS::Batch:JobQueue Properties: ComputeEnvironmentOrder: - ComputeEnvironment: !Ref 'ComputeEnvironment' Order: 1 JobQueueName: JobQueue Priority: 1 State: ENABLED
CloudWatch Events
毎日、日本時間0時0分にトリガーをするルールを作成
Rule: Type: AWS::Events::Rule Properties: Description: Batchのトリガー 毎日0時 JST Name: Rule ScheduleExpression: cron(0 15 * * ? *) State: ENABLED Targets: - Arn: !Ref 'JobQueue' BatchParameters: JobDefinition: !Ref 'JobDefinition' JobName: every-batch-job Id: Every-Rule-Id RoleArn: !GetAtt 'BatchServiceRole.Arn'
GitHub Actions に使用するユーザ関連
ここでのテンプレートは上で作ったテンプレートとは別で作成しています。
またパラメータ等は時間の都合上、簡易的なものにしています。
Parameters: JobDefinition: Type: String Default: JobDefinition Description: AWS::Batch::JobDefinitionのリビジョンを含まないArn MinLength: 1 MaxLength: 100 Rule: Type: String Description: AWS::Events:RuleのArn MinLength: 1 MaxLength: 100 BatchServiceRole: Type: String Description: AWS::IAM:RoleのArn MinLength: 1 MaxLength: 100 Resources: GitHubActionsUser: Type: AWS::IAM::User Properties: ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser - arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess UserName: github_actions GitHubActionsUserPolicy: Type: AWS::IAM::Policy Properties: Statement: - Action: - batch:DescribeJobDefinitions Effect: Allow Resource: - '*' - Action: - batch:RegisterJobDefinition Effect: Allow Resource: - !Ref 'JobDefinition' - Action: - batch:DeregisterJobDefinition Effect: Allow Resource: - !Sub '${JobDefinition}:*' - Action: - events:PutTargets - events:RemoveTargets Effect: Allow Resource: - !Ref 'Rule' - Action: - iam:PassRole Condition: StringEquals: iam:PassedToService: - batch.amazonaws.com - ec2.amazonaws.com Effect: Allow Resource: - !Ref 'BatchServiceRole' PolicyName: GitHubActionsUserPolicy Users: - !Ref 'GitHubActionsUser'
CFn 適用後、アクセスキー及びシークレットアクセスキーを作成して保存しておく。
GitHub Actions の設定
タグがプッシュされたときトリガーをするアクションを作成しています。
また、リポジトリの Settings
-> Secrets
より以下の環境変数を追加しています
AWS_ACCOUNT_ID
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_ECR_REPO_NAME
AWS_BATCH_SERVICE_ROLE
AWS_BATCH_JOB_QUEUE_ARN
# .github/workflows/main.yml name: CD on: push: tags: - v* jobs: build: runs-on: ubuntu-18.04 timeout-minutes: 600 steps: - users: actions/checkout@2 - 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-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Get version number from tag id: extract-version shell: bash run: echo "##[set-output name=version;]$(echo ${GITHUB_REF#refs/tags/v})" - name: Build tag & push image to Amazon ECR id: push-ecr env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }} IMAGE_TAG: ${{ steps.extract-version.outputs.version }} shell: bash run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "##[set-output name=image;]$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name: Get current JobDefinition revision id: get-current-job-definition shell: bash run: | if [[ ! $(which aws) > /dev/null ]]; then apt-get update && apt-get install -y awscli fi REVISION=$(aws batch describe-job-definitions \ --job-definition-name JobDefinition \ # CFn で作成したジョブ定義の実際の名前 --status ACTIVE \ --query "jobDefinitions[0].revision") echo "##[set-output name=revision;]$REVISION" echo "##[set-output name=job-definition;]JobDefinition" - name: Register new JobDefinition id: register-new-job-definition env: ROLE_ARN: ${{ secrets.AWS_BATCH_SERVICE_ROLE }} ECR_IMAGE_NAME: ${{ steps.push-ecr.outputs.image }} JOB_DEFINITION_NAME: ${{ steps.get-current-definition.outputs.job-definition }} shell: bash run: | NEW_REVISION=$(aws batch register-job-definition \ --job-definition-name $JOB_DEFINITION_NAME --type container \ --container-properties "{ \"image\": \"$ECR_IMAGE_NAME\", \"vcpus\": 2, \"memory\": 2048, \"jobRoleArn\": \"$ROLE_ARN\" }" \ --query "revision") echo "##[set-output name=revision;]$NEW_REVISION" - name: Update CloudWatch Events Rule shell: bash env: CURRENT_REVISION: ${{ steps.get-current-job-definition.outputs.revision }} NEW_REVISION: ${{ steps.register-new-job-definition.outputs.revision }} JOB_DEFINITION_NAME: ${{ steps.get-current-definition.outputs.job-definition }} JOB_QUEUE_ARN: ${{ secrets.JOB_QUEUE_ARN }} ROLE_ARN: ${{ secrets.AWS_BATCH_SERVICE_ROLE }} run: | RULE_NAME=Rule TARGET_ID=Every-Rule-Id JOB_NAME=every-batch-job JOB_DEFINITION_ARN=arn:aws:batch:ap-northeast-1:${{ secrets.AWS_ACCOUNT_ID }}:job-definition/$JOB_DEFINITION_NAME NEW_JOB_DEFINITION=$JOB_DEFINITION_ARN:$NEW_REVISION OLD_JOB_DEFINITION=$JOB_DEFINITION_ARN:$CURRENT_REVISION # 古いターゲットを削除する aws events remove-targets --rule $RULE_NAME --ids $TARGET_ID # CloudWatch Eventsのターゲットを更新する aws events put-targets $RULE_NAME \ --targets "[ { \"Id\": \"$TARGET_ID\", \"Arn\": \"$JOB_QUEUE_ARN\", \"RoleArn\": \"$ROLE_ARN\", \"BatchParameters\": { \"JobDefinition\": \"$NEW_JOB_DEFINITION\", \"JobName\": \"$JOB_NAME\" } } ]" # 古いリビジョンを解除する aws batch deregister-job-definition --job-definition $OLD_JOB_DEFINITION
という感じで...
やってることは
- AWS CLI をインストール後にする
aws configure
作業 - ECR にログインをする
- プッシュされたタグ (
v*
) からバージョンを取得する docker build
→docker push
- CLI から最新のジョブ定義のリビジョンを取得する
- CLI から置き換えるジョブ定義を作成する
- CLI で CloudWatch Events のルールから現在のターゲットを削除→新しいターゲットを作成→古いジョブ定義を終了
put-targets
全部置き換えかなって思ってたけど CREATE の動作しかしかなった- 既存のターゲットを更新するような API がなかった *3 ので削除→作成のような面倒なことになった
最後に
今回は、 GitHub Actions を使って、 AWS の ECR とか Batch とか CloudWatch Events を更新する継続的デプロイメントな環境を実装した。
こういう CI/CD 環境を作るのってお金になるって小耳にしたんだけどマジ!?
最初は、手動でもいいやって思ってたけど普通に僕がいなくなったあと誰がこれメンテすんの?事案が(脳内)会議で勃発したので GitHub Actions の勉強がてら実装をしてみた。使用制限も結構ミニマムなプロジェクトだったらガバガバ(悪いことはしてないし悪い意味でもない)なのでなので制限食らうことないなぁっていう感じで他の CI サービス食いに言ってるなぁという感覚がすごい。
iOS アプリとかの CI/CD 環境にも利用されることがあるとかないとかサービスリリース当初に Twitter で見かけたけど macOS
環境下の制限が結構厳しいし Xcode
のバージョンがどのぐらい早く更新されるかっていうの結構重要だと思うので今のところは Bitrise 一択だなと個人的には思っています。
詳しく比較検証はしてないんですが、 CircleCI の machine
ビルドと GitHub Actions のコールドスタートは後者優勢かなぁっていう感覚がありました。 CircleCI では machine
を設定をしてビルド書けると30~60秒かかるらしい。 *4
あと、これは今は知らないけど CircleCI の AWS 設定をしたときに1ステップしか CLI で作業ができないような謎仕様があるのでこれをしなくても aws-actions/configure-aws-credentials
を使うことでフローの最後までクレデンシャルが通るのは熱い!!
AWS の ポリシーは 2048 bytes の上限があるのであんまり1つのユーザで実装を楽にしたい場合とかはユーザを増やしてみたいになるかなとは思います。自身は前に書いた CFn の記事 *5 のやつと ECR
, Batch
, Events
更新用の権限を今回作ったユーザに割り当ててあげてます。
Lambda での実装でもよかったんですが *6 、 RDS Proxy が現状、 Aurora for MySQL か RDS for MySQL にしか対応されていない *7 ので DB アクセスを伴うバッチ処理は、 Batch かなと思って使うことにしています。(2020年3月29日(日) 12:08現在)
最近溜まってきたブログネタ帳からようやく1つ解消することができた。(ネタ帳に山程ネタが溜まっているという話ではない。)
早く Aurora for PostgreSQL
で RDS Proxy
使いたい...
アホだからこの記事4時間も書いてたらしいwwwww
参考
*1:外部ライブラリに依存した Labmda 関数のデプロイパッケージの配置された S3 バケット
*2:サブネットの MapPublicIpOnLaunch に 'true' を設定
*3:events — AWS CLI 1.22.63 Command Reference
*4:Choosing an Executor Type - CircleCI
*5:troposphereで作ったAWS CloudFormationのテンプレートをCircleCIのジョブで検証する - 『入る学科間違えた高専生』の日記
*6:別のシステムではこんな感じの CD 環境の実装経験あり
*7:PostgreSQL を使っています