Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active June 1, 2023 08:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/dac45c36e32d9b847e5491c1e2f6135c to your computer and use it in GitHub Desktop.
Save yano3nora/dac45c36e32d9b847e5491c1e2f6135c to your computer and use it in GitHub Desktop.
[dev: AWS Copilot]

Overview

AWS Copilot CLI
aws/copilot-cli - github.com

ECS のデプロイツール、サービスタイプを選択 + Dockerfile を用意するだけで ECS 上にアプリを展開できる。Amplify が Front-end のデプロイ & AaC (Architecture as Code) ツールだとしたら、こちらは Back-end の AaC ツールといえる。

ぱっと見 ↓ みたいな機能構成の理解でいいのかな。

  • マイクロサービスアーキテクチャっぽく Application を複数 Service で構成
    • Request-Driven Web Service (App Runner、制約多め)
    • Load Balanced Web Service (ALB + ECS Fargate)
    • Backend Service (endpoint のない ECS Fargate)
    • Worker Service (ECS Fargate + Amazon SNS + Amazon SQS)
  • Service の他に Job も登録できる
    • いまは Scheduled Jobs (cron like な定期実行) だけ
  • 構築した Application は dev, prod など各 Environment ごとに展開
  • Application のデプロイについて CodePipeline で CI/CD を構成
    • Source Stage: repo の push を hook に pipeline を起動
    • Build Stage: repo から code を取得 => docker build し ecr へ push
    • Deploy Stage: テストや主導承認やらして rolling update を実行

本番環境での利用は ↓ あたり読んでから選定。

AWS Copilotで本番環境をコンテナ化する

Current Updates

v1.23

https://github.com/aws/copilot-cli/releases/tag/v1.23.0

  • aurora serverless v2 に対応

v1.16

https://github.com/aws/copilot-cli/releases/tag/v1.16.0

v1.15

https://github.com/aws/copilot-cli/releases/tag/v1.15.0

  • app runner が vpc に対応

AaC, IaC

Architecture as Codeってなぁに? 〜Infrastructure as Codeを超えて〜

copilot は terraform, serverless, cdk なんかと比べると設定項目が少なく、あくまでも「 ECS のベストプラクティスな service 構成を手軽に作る 」ことに注力してるぽい。

この手のやつ、デザインされた範囲内の調整は気軽だが、そこを外れると辛い。amplify 同様に ↓ っぽく新規プロジェクト立ち上げ時のテンプレとして使い、どこかで見切って移行する前提の運用がよさそう。

  • 素早く「よくある形」を構成する
  • ツールの「カスタマイズ・調整の範囲」を見越した設計をする
  • 規模が拡大したら潔く捨てる (体制、ツール、アーキテクト再選定する)

既に aws インフラ設計をまるっと頭でできるくらいなら、cdk で柔軟に組むほうが絶対楽。でもその頭がないうちは、こういうテンプレセットで「よくある形」を構成してくれることにありがたみを感じる。

Opration Tips

とりあえず初回の立ち上げは miss ると時間溶けるので必ず service の count (タスク数) を 0 にして CREATE => ROLLBACK 落ちだけは回避しておくといい。

  • CREATE からの ROLLBACK はキャンセルできず、ROLLBACK がマジで時間かかる
  • 初回の deploy でコケると log group が消えて copilot svc logs 見れない
  • 当然だけど、事前に開発環境のテスト通過や、ブランチ、コミットの整理しておく
    • なんだかんだサクッとはいかないので、コード安定状態 & しっかり時間とってやること

あと *_IN_PROGRESS で待たされているときは ecs の web console から task 数を 0 にしてキャンセルすればいい。

copilot svc deploy keep running due to task healthcheck failure #1580

Trouble Shooting - 色々な罠にハマった際のメモ

ECS Deployment Circuit Breaker で失敗する

  • ECS タスク起動失敗なので copilot svc logs や cloudwatch logs で error check して修正する
  • DB がないとか、疎通できてないのに migrate しようとして exit 1 になってたり

IN_PROGRESS が長い & コンテナ起因のときはタスク数 0 にして即終了させる

  • ECS タスク定義からタスク数 0 にしてやると health check が入らず「アプリ起因の不具合でタスク起動しない」を回避できる
  • aws/copilot-cli#1180 (comment)

実行者の IAM Role どうするか問題

  • 構成に必要な policy が明文化されてない問題ある
  • aws/copilot-cli#1345
  • 基本アプリまるごと aws アカウントを分離して admin 権限で試したほうが良さそう

Health Check で落ちる

ALBのヘルスチェックでHealth checks failed with these codes [302]が出た場合の対処方法
Fargate での Amazon ECS タスクのヘルスチェックの失敗をトラブルシューティングするにはどうすればよいですか?

  • うっかりで多いのは / path がログイン認証必要で 300 系で next-auth とか使ってて redirect されてるパターン
    • ↑ なら manifest.yml の healthcheck を 200 返すパスに変更しておけばいい
  • 単純ミス (build 時の args 漏れとか) で普通に立ち上がってないとか、その辺もありえる
  • port 3000 のとき alb target group の protocol が HTTPS になっていて落ちていた
    • この辺理解浅いが、手動設定が必要とか ... ?
    • 一旦 healthcheck.port を設定したらうまくいったぽい?
    • aws/copilot-cli#3772
    • 80 の default target group は落ちてるけど、rules で追加される正しい port の target group で healthcheck が通るみたい?

SIGKILL 137

ECS だけかどうかは不明だが ENTRYPOINT or CMD で jest とかある程度時間かかるテスト系走らせると SIGKILL 137 で落ちる、そんなとこに仕込むなといえばそうなんだけど ... 。

domain

  • domain まわり、証明書の登録状況によって validation 通過しない問題ある
  • aws/copilot-cli#3347
  • --import-cert-arns で回避するか、作り直すかって感じか
  • 過去に利用した app name だとコケるらしい?

delete できない => stack sets 消して 1 からやり直す

secrets は全部登録されてないとだめ

  • Check if secrets exist before deploying the workload #3246
  • copilot secret init で登録してないやつを yml に書いてると起動しない
  • ダミー値でもいいから登録してから deploy してやる
  • Dockerfile の entry point で migrate とかする場合は DATABASE_URL とか正常じゃないと当然コケる、DB は addon で載せてたりするはずなので、先にできるだけ外部連携しない状態で deploy してやったほうがいい
  • addon の秘匿情報依存な container 構成 (addon で立てた redis の秘匿情報を parametor store から渡せる状態になってないと task の health check 通らないとか) のときは、ちょっとハックだけど一度 fail => retry するくらいまで待って addon の起動・接続情報確認して parametor store 書き換えでいける

Getting Started

Deploy your first application
Commands - init
AWS Copilot のご紹介
AWS Copilot によるコンテナアプリケーションの自動デプロイ

  • docker container の deploy tool なので docker 起動してないと cli コマンド一部動かないので注意
  • cli で定義した各 app, service などの meta データは ssm の parameter store に入ってる
  • 初回 init 時に domain を割り当て or ACM 証明書を割り当てないと https 化が面倒なので注意
$ brew install aws/tap/copilot-cli

# Nginx の Dockerfile と index.html だけの sample repo を clone
#
$ git clone https://github.com/aws-samples/aws-copilot-sample-service example
$ cd example

# AWS_PROFILE 環境変数で app で利用する iam user の credential を流す
# ecr や pipeline で利用する環境は env 別に設定できないっぽいので
# app init の直前で指定、一度設定できればあとは大丈夫かは検証してない
#
$ export AWS_PROFILE=sandbox

# 初期化 & 対話で app の名称とタイプを設定
# copilot init でもいいが、このタイミングで --domain でドメイン指定しないと
# https 化されない & ドメイン指定できないっていう罠があるのでここでやっとく
# ドメイン名は route53 で作成 hostzone 登録してるものを指定
#
$ copilot app init --domain example.com
# 
# Application name: xxx
#
# Which workload type best represents your architecture?
# > Request-Driven Web Service  App Runner であげるやつ
#   Load Balanced Web Service   よくある ALB x ECS Fargate 構成
#   Backend Service             endpoint なしの ECS Fargate
#   Worker Service              SNS x SQS 購読する ECS Fargate
#   Scheduled Job               定期実行させる ECS Fargate task

# service の追加
# copilot init だと app, env, svc を一度にやる感じっぽい
# ただ、何が起きてるか全然わからんし勝手に構築されると面倒なので
# いまのところ個別にやったほうがよさげ、 svc init は全 env に適用されるぽい
#
$ copilot svc init --name myapp

# test 環境 (env) 構築の設定 => aws profile の選択
#
$ copilot env init
#
# Environment name: test
# Credential source: [profile xxxx]
#
#   Would you like to use the default configuration for a new environment?
#     - A new VPC with 2 AZs, 2 public subnets and 2 private subnets
#     - A new ECS Cluster
#     - New IAM Roles to manage services and jobs in your environment
#   [Use arrows to move, type to filter]
#    > Yes, use default.
#      Yes, but I'd like configure the default resources (CIDR ranges).
#      No, I'd like to import existing resources (VPC, subnets).
#
# vpc 構成とか聞かれる、独自構成にしないなら default でよさそう

# 先に env だけでも deploy しておかないと secrets とか設定できない
#
$ copilot env deploy --name prod

# app 全体のデプロイ
# 個別デプロイは copilot svc deploy -n name -e env とか
#
$ copilot deploy

# fargate 上の container に入るなら
# 別途 ssm session manager plugin の install 必要
# https://go.aws/3qWnSbr
#
$ copilot svc exec -a my-app -e test -n frontend

# log は svc logs で見れる
# --follow は tail っぽくみる option
# log の出方は log stream として区切られてでる関係で
# limit 指定ちゃんとしてないといい感じに出てくれない
#
$ copilot svc logs --follow --limit 50
#
# サクッと調べるなら since で 1h とかのほうがいいかも
# ちゃんと見るなら cloudwatch > logs insights のほうがいい
#
$ copilot svc logs --since 1h

# 作成した app を削除してチュートリアルおしまい
# local の copilot/ 配下は .workspace ファイル以外残る
#
$ copilot app delete

AWS Account Credentials

認証情報に AWS Root アカウントのものは使えないらしいので IAM User 作って使えとのこと。

Credentials

あとクロスアカウント利用する前に app と env でアカウントが内部的に別管理されていることに注意。

Support for cross-account access with copilot pipeline #2533

app 用の profile を指定したい場合は export AWS_PROFILE などで環境変数を介して読ませる必要があるみたい。code pipeline や ecr ではこの app の profile を利用するっぽいので ...

  • code pipeline を実際に動作させる prod の aws 環境の iam profile を AWS_PROFILE で読ませる
  • test, staging などを別の aws 環境の iam profile 指定して構成する

... のがいいか?

Concepts & Manifest

Concepts
Manifest - 実際の manifest.yml サンプル

基本的には Dockerfile と manifest.yml で 1 service を ecs fargate にぶち上げるっていう作りみたい。裏側では manifest.yml に応じて CloudFormation を組んでるぽい。

Unit Desc
Application Service と Environment を包括する ... frontend と api という 2 つの service を持った app みたいな。
Environment AWS リソースを dev, とか prod とかの環境毎に別ものとして展開してくれるやつ。
Service 実行したい code とその周辺 infra をまとめた概念、web server を作りたいなら、express の Dockerfile + code に対して ALB, ECS Fargate を作成してくれる。Manifest で構成管理するぽい。
Job 何らかの Event によって発火する、常駐しない短命の ECS Task 。こちらも Manifest で構成や実行スケジュールを管理する。
Pipeline よくある GitHub なんかと連携する CI/CD の構成をしてくれる、AWS CodePipeline を裏側で動かしてるのかしら。

Docker Image Config

Image Config

image:
  build:
    dockerfile: path/to/dockerfile
    context: context/dir
    target: build-stage # マルチステージビルドならここで stage 指定

Sidecar

Sidecars

service のメインとなる docker container は 1 つだが、Sidecar という形で補助 container を一緒に上げることもできるっぽい。この場合同じ unit (task 定義?) としてデプロイされるっぽく、volume mount も可能みたい。

Storage

Storage
storage init

  • Service や Job に DynamoDB, S3, Aurora (Serverless) を追加できる
  • 内部的には後述の Addon として追加されるよう
  • Aurora Serverless 以外の RDS はまだ未サポートぽい ... 残念
# Aurora Serverless をぶちあげる
$ copilot storage init -t Aurora
$ copilot deploy --name servicename
# copilot/servicename/addons/mycluster
Mappings:
  myclusterEnvScalingConfigurationMap:
    # このへんお好みに
    All:
      "DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128
      "DBMaxCapacity": 2   # AllowedValues: from 0.5 through 128

Resources:
  # ...
  nextjsclusterDBClusterParameterGroup:
    # このへんお好みに
    Properties:
      Family: 'aurora-postgresql14'
  nextjsclusterDBCluster:
    # このへんお好みに (postgres 15 系は public の仕様がガラッとかわるので注意)
    Properties:
      Engine: 'aurora-postgresql'
      EngineVersion: '14.4'

deploy 後は secrets に認証情報が入るっぽくて、これを自分で copilot secret init で parameter store に入れてつかえよということらしい ... でもほとんどのライブラリで DATABASE_URL みたいな形式で読みたいから変換が必要で使えない 本当に aws さんってそういうとこあるよね

process.env.MYCLUSTER_SECRET // { host: xxx, username: xxx, password: xxx }

現状、回避策としては docker の build entrypoint 時点で MYCLUSTER_SECRET ばらして自前で DATABASE_URL をセットする ... とかしかなさそう。

Compose variable from other variables in manifest.yml #4790
construct DB URLs from DB Secrets #2483

# json なので jq 入れておく
RUN curl -o /usr/local/bin/jq -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && chmod +x /usr/local/bin/jq
RUN chmod 777 entrypoint.sh

CMD [ "bash", "-c", "entrypoint.sh && npm start" ]
#!/bin/bash

# NOTE https://github.com/aws/copilot-cli/issues/4790
#
# copilot が生成する CLUSTER_SECRET が runtime 注入かつ json とかいう
# 意味不明仕様なので entrypoint で json to DATABASE_URL を生成している
# prisma は `.env` という名前のファイルしか解釈しないので要注意
# ref. https://www.prisma.io/docs/guides/development-environment/environment-variables
#
host=$(echo $MYCLUSTER_SECRET | jq -r '.host')
port=$(echo $MYCLUSTER_SECRET | jq -r '.port')
dbname=$(echo $MYCLUSTER_SECRET | jq -r '.dbname')
username=$(echo $MYCLUSTER_SECRET | jq -r '.username')
password=$(echo $MYCLUSTER_SECRET | jq -r '.password')

# 本当なら dockerfile で ENV したいが
# runtime 注入しかされないっぽいので entrypoint でうにょる
# next.js や prisma とかなら .env で回避したり、そうでなくても dotenv でなんとかなるか
# 当然、こいつの後続に db migrate とかを置く必要ある
#
echo DATABASE_URL="postgres://${username}:${password}@${host}:${port}/${dbname}?schema=public" >> .env

Addon

Additional AWS Resources

storage など、用意されているもの以外の依存リソース (S3 や DynamoDB や RDS など) を、Addon という形で構成できるぽい。

コマンドなどはないので自分で addons/ ディレクトリを追加して CloudFormation テンプレ (yml) をぶちこむぽい。特に親である svc の manifest に追記したりはしないみたい。

└ copilot
  └ webhook               # すでに init 済みの svc の下に
    ├ addons              # こいつを生やす
    │ └ mytable-ddb.yaml  # ここに cfn yml 書く
    └ manifest.yaml

CloudFormation については AWS CloudFormation > User Guide あたりを。

代替のケースでは生成と同時に secret に秘匿情報入れてもらって、parameter store から読み込む感じぽい。

# 作ったら svc 単位で deploy してやる感じ?
$ copilot svc deploy --name webhook

with Secrets

addon で生成した resource (rds とか) の接続情報を secrets 経由で渡さないとコケるような container (svc) は、deploy 前に 予め起動タスクを count: 0 で落として health check 通過させつつ addon deploy して、secrets に正しい接続情報入れてから再度 deploy する か、cloudformation template 側で 適切な parameter store を切ってそれを svc が読める状態で deploy させる ような工夫が必要。

ElastiCache

Could we get an example of a Redis Elasticache addon? #2011

↑ に EngineVersion: "6.2" とか追加したら ok でした。

secrets:
  REDIS_URL: /${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/quirrel/redis
Parameters:
  App:
    Type: String
    Description: Your application's name.
  Env:
    Type: String
    Description: The environment name your service, job, or workflow is being deployed to.
  Name:
    Type: String
    Description: The name of the service, job, or workflow being deployed.

Resources:
  # Subnet group to control where the Redis gets placed
  RedisSubnetGroup:
    Type: AWS::ElastiCache::SubnetGroup
    Properties:
      Description: Group of subnets to place Redis into
      SubnetIds: !Split [ ',', { 'Fn::ImportValue': !Sub '${App}-${Env}-PrivateSubnets' } ]

  # Security group to add the Redis cluster to the VPC,
  # and to allow the Fargate containers to talk to Redis on port 6379
  RedisSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Redis Security Group"
      VpcId: { 'Fn::ImportValue': !Sub '${App}-${Env}-VpcId' }

  # Enable ingress from other ECS services created within the environment.
  RedisIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress from Fargate containers
      GroupId: !Ref 'RedisSecurityGroup'
      IpProtocol: tcp
      FromPort: 6379
      ToPort: 6379
      SourceSecurityGroupId: { 'Fn::ImportValue': !Sub '${App}-${Env}-EnvironmentSecurityGroup' }

  # The cluster itself.
  Redis:
    Type: AWS::ElastiCache::CacheCluster
    Properties:
      Engine: redis
      EngineVersion: "6.2"
      CacheNodeType: cache.t3.small
      NumCacheNodes: 1
      CacheSubnetGroupName: !Ref 'RedisSubnetGroup'
      VpcSecurityGroupIds:
        - !GetAtt 'RedisSecurityGroup.GroupId'

  # Redis endpoint stored in SSM so that other services can retrieve the endpoint.
  RedisEndpointAddressParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub '/${App}/${Env}/${Name}/redis'   # Other services can retrieve the endpoint from this path.
      Type: String
      Value: !GetAtt 'Redis.RedisEndpoint.Address'

Outputs:
  RedisEndpoint:
    Description: The endpoint of the redis cluster
    Value: !GetAtt 'Redis.RedisEndpoint.Address'

RDS Postgres (not serverless)

Support RDS for storage #1178 > comment-963408231

↑ を利用して、事前に copilot secret init で DATABASE_PASSWORD をセット (DBName とか MasterUsername とかも編集したければやる) してから deploy でいけた。

あとは EngineVersion とか DBInstanceClass とか DBInstance 配下 Properties をチェックしてお好みで。

生成後に DATABASE_ENDPOINT が secure string じゃなくて string で parameter store に入ったり、db の識別子名が完全なランダムでひと目で判別できないのがちょっとあれだけど ... どうやって直すのかわからん。

あと parameter store に作った secure string の version 指定とか注意

#
# 最後の :1 の部分は secure string のバージョンなので
# 作り直したりしたら変更する必要ある
# ref. https://techblog.zozo.com/entry/pass_secrets_to_cloudformation
#
MasterUserPassword: !Sub "{{resolve:ssm-secure:/copilot/${App}/${Env}/secrets/DATABASE_PASSWORD:1}}"

Env Variables

Environment Variables
Manifest Env

# copilot/{service name}/manifest.yml の一部
variables:
  LOG_LEVEL: debug

# 環境ごとの set (このケースだと prod のみ override 扱い)
environments:
  production:
    variables:
      LOG_LEVEL: info

# shell の環境変数使うとか
image:
  location: id.dkr.ecr.zone.amazonaws.com/project-name:${TAG}
  biuld:
    args:
      NEXT_PUBLIC_FOO: bar # build 時の環境変数はここ


# Copilot の設定 (予約済み環境変数) を使うとか
secrets:
   APP_SECRET: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/app_secret

Environments

Environments
ECR image tags should have different value for each environments. #2927

manifest.yml の任意の値を override したい場合 (例えば image.build.args とか) は environments を使う。

image:
  build:
    dockerfile: Dockerfile
    args:
      FOO: bar # test 以外では bar

environments:
  test:
    image:
      build:
        dockerfile: Dockerfile
        args:
          FOO: baz # test env では baz

Secrets

Secrets

  • 基本 copilot secret init で parameter store 作成 => 環境変数で流す
  • どの環境向けか、など自動でタグ付けされ secure string 扱い
  • 作成した parameter は manifest.yml > secrets で key 指定すれば読み込める
    • DB_NAME: /.../.../db_user みたいな定義でアプリ側で環境変数として参照可能
  • secret init 時点で存在する全 env 向けの parameter store を作成する
    • 裏を言うと env 毎に init できないことに注意
    • ただし --overwrite を付けなければ「存在するやつは skip 」になるので、新規作成に限りなんとかなる
      • なんなら、enter だけ押すと生成処理も省略されてるっぽい?
# 前もって copilot storage init などで
# リソース作成 & 秘匿情報を AWS Secrets に作成し
# console から読み取り => メモっておく
#
# ↑ 秘匿情報を secret init で登録する
# 自動的に parameter store に secure string として
# 登録してくれる (/copilot/<app>/<env>/secrets/<secret> みたいな key)
#
$ copilot secret init
# あとは yml の environments とかで各環境向けに secrets 定義してやる
environments:
  prod:
    secrets: 
      DB_PASSWORD: /copilot/my-app/prod/secrets/db_password
  dev:
    secrets:
      DB_PASSWORD: /copilot/my-app/dev/secrets/db_password

# まぁ ↑ くらい単純なら copilot の予約済み環境変数でやってもよき
secrets:
    database_url: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/database_url

↑ の手順以外で作成した parameter store の secure string を読み込む場合は ...

  • app と env をタグ付け
    • copilot-application: copilot app 名
    • copilot-environment: copilot env 名
  • ↓ のように yml の secrets で parameter name を指定
secrets:
  GITHUB_WEBHOOK_SECRET: GH_WEBHOOK_SECRET  

Secrets for Build Stage

Support docker build-time secret #4230

  • 現状、build 時の args で secrets を使うことはサポートされてない
  • が、一応 pipeline 作って buildspec.yml の env セクションで読み込んで shell 変数を使うことはできる
  • ので以下のように pipeline を介せば docker の build 時点で secrets を参照することは可能ぽい

以下 nextjs の NEXT_PUBLIC_ 環境変数を build 時にセットしておき、inline 化する例。

業務でやったやつ (https://github.com/sousaku-co-ltd/acas/pull/3#discussion_r1212728409)

# Dockerfile
FROM node:16-buster
EXPOSE 80
WORKDIR /app
COPY ./ ./
RUN npm ci

# このへんに列挙
ARG NEXT_PUBLIC_SOME_ENV

# ここで NEXT_PUBLIC_ 接頭辞付きが inline 化される
RUN npm run build
CMD npm start
# buildspec.yml
env:
  parameter-store:
    NEXT_PUBLIC_SOME_ENV: /copilot/appname/envname/secrets/NEXT_PUBLIC_SOME_ENV
# manifest.yml
image:
  build:
    dockerfile: Dockerfile
    context: .
    args:
      # buildspec.yml > env > parameter-store
      NEXT_PUBLIC_SOME_ENV: ${NEXT_PUBLIC_SOME_ENV}

ちなみに NEXT_PUBLIC_ 接頭辞がないやつは next が自動的に front に inline 化してくれない (多分 build 時の process.env に不要な shell 変数をのせないようにしてる) ので、env ファイルなどで別途吸わせる工夫が要る。

# .env.copilot
FRONT_ENV_WITHOUT_NEXT_PUBLIC=${FRONT_ENV_WITHOUT_NEXT_PUBLIC}
RUN apt-get update
RUN apt-get install gettext-base

ARG FRONT_ENV_WITHOUT_NEXT_PUBLIC

# 本番環境限定の .env を envsubst でテンプレートファイル (.env.copilot) をバラして生成
RUN cat .env.copilot | envsubst > .env.production
// next.config.js
const nextConfig = {
  env: {
    FRONT_ENV_WITHOUT_NEXT_PUBLIC: process.env.FRONT_ENV_WITHOUT_NEXT_PUBLIC,
  },
}

module.exports = nextConfig

Domain

https://aws.github.io/copilot-cli/ja/docs/developing/domain/

ドメインは app init 時にしか設定できないっぽいので注意。

Service Discovery

https://aws.github.io/copilot-cli/ja/docs/developing/service-discovery/

service 間での連携 (別 service の検出) も可能。AWS 内部の private address と DNS による mapping で通信するっぽいので、VPC とか public の endpoint とか気にしなくていいみたい。

具体的には特別な環境変数 COPILOT_SERVICE_DISCOVERY_ENDPOINT を利用して通信する。これは service 上で <env>.<app>.local のように変換される。

service 上で http://<service>.test.myapp.local:8080 のような endpoint へ通信をすると、自動的に vpc 内で private ip address へ変換され myapp app の test env の <service> service の :8080 port へ routing してくれるってワケ。

// この例では、ある service から同じ copilot の app 内で定義した
// api という service に対し、同一 vpc の service 検知 のための
// 特別な local endpoint (COPILOT_SERVICE_DISCOVERY_ENDPOINT) を
// 介して private network 内で通信している
//
endpoint := fmt.Sprintf(
    "http://api.%s:8080/some-request",
    os.Getenv("COPILOT_SERVICE_DISCOVERY_ENDPOINT"),
)

Usage

Request Driven Web Service (App Runner)

App Runner が VPC いけるようになるまでは Request Driven なんちゃらは触れなくていい気持ち 最近 VPC イケるようになったぽい。

App Runner の新機能 — Amazon Virtual Private Cloud (VPC) をサポート

Load Balanced Web Service

  • internet (IGW) からの public access を ALB or NLB で捌いて container にわたすやつ
  • http の有効化で ALB が構成され 80, 443 listen => image.prot へ mapping
  • nlb 有効化すると NLB が構成され tcp, udp の色んな port listen ができる

Problem create another loadbalancer #3013 で言われてるように load balancer は app の全 service が共有する こと。app 内で alb, nlb それぞれ 1 つずつしか持てないので、alb の path がかぶったり nlb の port / protocol がかぶったりは ng 。

http.path によりパスで対応 svc を分割はできるが、複数の path: '/' な svc は構成できないので注意。複数 port を internet 公開したいなら nlb でやるしかない。

EXPOSE と image.port

  • Dockerfile で port の EXPOSE 必須ぽい
  • image.port: は 80 にしとくと ALB の health check サクッと通せる?
    • 3000 とかでも動作するんだけど、ALB の health check が HTTPS で 3000 に疎通確認して NG くらっていた (HTTP にしたら通った) ことがあった

基本 ALB メインで使ったほうがいい気がする

とはいえ nlb にしなくても、手動で backend service に対して target group 生やして、alb に listener 登録すれば port アクセスもさせられるし、nlb と比べて柔軟な security group 設定できるので (固定 ip 制限とか) nlb にするメリットあんまり無い気がするけど ... 。

ref. VPC 上の private ECS サービスへ ALB Target Group から固定 IP のみアクセス許可する

taskdef_overrides

container が api と gui を別 port で提供しているようなケースで ...

  1. HTTP/HTTPS で ALB 受け
  2. container の 1080 port へ流す (web gui)
  3. 同 container backend 通信向けに 1025 port も開放 (smtp)

... みたいなことできるぽい。

http:
  path: '/'

image:
  location: schickling/mailcatcher
  port: 1080

# https://aws.github.io/copilot-cli/docs/developing/taskdef-overrides/
taskdef_overrides:
  - path: "ContainerDefinitions[0].PortMappings[-].ContainerPort"
    value: 1025
  - path: "ContainerDefinitions[0].PortMappings[1].Protocol"
    value: "tcp"

NLB - Network Load Balancer

Nlb - manifest

NLB は ALB と違って security group とかつけられなかったり、アクセスログを終えなかったりするので、public access を直接捌くというより「捌いたあとの中継」とか「 HTTP 以外など ALB が対応してない protocol 」に使ったほうがいい。

/api みたいなのをお外に出したいなら、素直に path でわけてプログラム側で対応し、alb を選択したほうが無難な気がする。

2021年6月時点AWS ALBとNLBの機能比較と使い所の検討

http: false

nlb:
  port: 8080/tcp

Pipelines

Pipelines

  • CI/CD を CodePipeline で構成できる
  • 現状 db migration みたいなコマンドを実施する場所は提供されてない
  • v1.16 で main ブランチを prod env へ ... という使い方に対応した
  • slack 通知みたいなのは手動構成が必要
    1. pipeline 作って 1 度回す
    2. pipeline から通知管理 > sns topic 作成
    3. chatbot で slack client 作成し ↑ sns を購読する channel 作成
  • CI を回すコンテナは docker v18 くらいで Dockerfile の書き方古いことに注意
    • COPY . . みたいな複数ファイルコピーは COPY . ./ みたいにすること
  • 当然だけど env に対して deploy するので env init 最初にやっておくこと
    • deploy したい svc or job は全て先に手動で deploy しておく必要があるので注意
  • test_commands とか、どういう使い方するのかいまいちわからん

manifest.yml を local の docker-compose.yml と対応 させて、こっちの pipeline.ymlは local 開発時の provisioning script と対応 させる感じで構成すると良さそう。

# 設定ファイルの初期化
$ copilot pipeline init

# stages あたりで CI 構成
$ vi copilot/repo-branch/pipeline.yml

$ git add .
$ git commit -m "Adding pipeline artifacts"
$ git push

# 編集した yml に従い構成を AWS 上へ展開
# このとき github 連携を求められ aws console に飛ばされるが
# region が意味不明なとこになってて混乱する、aws profile の
# region に行けば「保留中」の接続があるので、そいつを更新してやる
#
$ copilot pipeline deploy

# pipeline の削除
$ copilot pipeline delete
# pipeline.yml

source:
  provider: GitHub
  properties:
    branch: main  # main へ push => CI が回る
    repository: https://github.com/xxx/xxx

stages:
    -
      name: test
      # make test コマンドと echo コマンドが
      # test 環境で正常終了した場合のみ、次の
      # prod 環境でのデプロイ stage へ進む ... という感じ?
      test_commands:
        - make test
        - echo "woo! Tests passed"
    -
      name: prod
      # デプロイ前に手動承認を挟めるぽい
      requires_approval: true

Changing Branch

  1. manifest.yml の branch を変更してから copilot pipeline deploy
  2. ↑ やったあとに当該 branch で適当な空 commit 作って push (または再試行でも ok)

これで branch を変更できるので、最初は add-copilot-pipeline みたいな pipeline 追加用開発ブランチで作業、push して pipeline を安定させてから、最後に main に変更するといい。

Multiple Pipeline for git branches

ブランチごとにマルチパイプラインを作成する
Pipelines > ステップ 1: Pipeline の設定

多分だけど、ドキュメントをざっと読んだ感じだとこう ... 。

  • copilot pipeline init 時に聞かれる pipeline name を ${REPOSITORY}-${BRANCH} 形式にする
  • copilot/pipelines/${REPOSITORY}-${BRANCH}/manifest.yml が生成される
  • あとは追跡ブランチ指定や、展開先 (env) 指定を yml でうにょれって感じか

Docker pull rate limit

最近 Docker Hub が匿名ユーザに対して pull 制限をかけた関係で docker login でユーザログインしないとたまに制限に引っかかっておちちゃう。

# buildspec.yml
env:
  # parameter store に適当に用意しておく
  parameter-store:
    DOCKER_USERNAME: DOCKER_USERNAME
    DOCKER_PASSWORD: DOCKER_PASSWORD
phases:
  install:
    runtime-versions:
      # ...
    commands:
      # ...
  build:
    commands:
      # ...
  post_build:
    commands:
      - docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD
      # ...

Worker Service

Publish/Subscribe - SNS と SQS を使った worker の queuing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment