Afternoon Log

日々のことや、技術的な備忘録を吐き出していくつもり

CircleCI からECSへ自動デプロイ

三部作の最終回。

前回ほぼほぼECRの方の話しかしてない気がする。
大事な部分、コードペタッと貼っただけじゃねーか!と言われても仕方ない。
そして残念、たぶん今回も。。。

  1. CircleCI で docker build するまで
  2. CircleCI からECRへ自動push
  3. CircleCI からECSへ自動デプロイ ← イマココ

ECS準備

初めに断っておくのですが、AWSチュートリアルを見ながら進めようと思っていたのですが
書いてあることがマジで分からなくなったので、その通りではありません。
認証のあるECRをIAMロールのを活用してタスク実行しようとか、
そういうのほんと……よく分かんない……学習コスト高くない……?
と言うわけで、
ちょっと強引だけど前回作ったCLIユーザーを利用する形にしました。

VPC

最初にサーバにネットワークのセキュリティ設定を進めて行きたいと思います。
この先webサーバを用意するのですが、
何も設定していないとそこまで到達しないので設定します。
AWSマネジメントコンソールからVPCを選択します。
VPCの利用に限っては料金が発生しないっぽいです。

VPCの作成をクリックして、各項目を埋めて作成です。

カラム 設定値
名前タグ my-web-vpc
任意ではありますが自分が何のためのVPCか識別できるよう付けておくのが良いかと
IPv4 CIDR ブロック 192.168.0.0/16
利用するプライベートIPv4アドレス空間
IPv6 CIDR ブロック なし
テナンシー デフォルト
サブネット

VPCができたら次はサブネットからサブネットの作成を行います。
すごくざっくり言うと、VPCで定義したアドレス空間を更に分ける感じのはず。
ここら辺のネットワーク周りはよりセキュリティを強固にしたり、
IP空間毎に役割を持たせたい時にじっくり設定しましょう。
単純な個人webサイト程度だとあまりここにコストをかけ過ぎるのも……って感じです。
なので、サブネットマスクも収まっていれば割と何でも良いかと。
/32とはか……そもそも設定できるのかな?

カラム 設定値
名前タグ my-web-vpc
VPC 作ったVPC
アベイラビリティーゾーン 指定なし
IPv4 CIDR ブロック 192.168.0.0/20
セキュリティグループ

さて、ネットワークが出来たのでそこにアクセスコントロールしていきます。
たぶんセキュリティグループが自動的に作成されていると思います。
名前とか付けておくと今後も分かりやすくなると思います。
作成されたセキュリティグループを選択したら
インバウンドのルールってタブがあると思いますので、
そこでルールの編集をクリックします。
インバウンドというのは、
外からAWSの方に行くリクエストですね。
HTTPリクエストを受け付けるため、以下のルールを追加します。

カラム 設定値
タイプ HTTP
プロトコル TCP
ポート範囲 80
ソース カスタム
0.0.0.0/0
説明 http request

今回はIPv4のHTTPのみなのでこれだけですが、
v6対応したら::/0を追加したり、
HTTPS対応したら443で受け付けるルールを追加します。

これでネットワークとセキュリティは一旦お終いです。

クラスター作成

ECS自体の構成はそんなに言及しませんが、
クラスターがサービスを束ね、
サービスが個々のタスクを管理するといった感じでしょうか。
このクラスターもCLIから作成することもできるのですが、
一度作ればそれでいいのでわざわざCLIから作るってことはしません。
あまりそれが求められる場面も少ないと思います。

話を戻して、
AWSマネジメントコンソールからECSを選択し、
クラスターからクラスター作成します。
今回はEC2 を利用してみようと思います。
各設定値は以下の通り。
なるべく無料枠で収めちゃう戦略です。

カラム 設定値
クラスタテンプレート EC2 Linux + ネットワーキング
クラスター名 任意
hogehoge-cluster
プロビジョニングモデル オンデマンドインスタンス
EC2 インスタンスタイプ t2.micro
12ヶ月無料枠
インスタンス 1
EC2 Ami Id Amazon Linux 2 AMI
EBS ストレージ 30GB
12ヶ月無料枠
キーペア なし
VPC 先ほど作成したVPC
サブネット 先ほど作成したVPCのサブネット
セキュリティグループ 先ほど作成したセキュリティグループ
コンテナインスタンスの IAM ロール ecsInstanceRole
無ければ自動生成される
ポリシーのアタッチ

またポリシーですか?
またポリシーです。ごめんなさいね。
今回はECS側のポリシー設定です。
クラスターは既に作成しているので、
タスク定義やクラスターで動かすサービス作成などの権限を扱うポリシーをアタッチします。
前回は既存のポリシーをアタッチしたのですが、
今回は自分でポリシーを作ってみます。

AWSマネジメントコンソールからIAMを選択し、ポリシーの画面まで遷移します。
ポリシーの作成から、以下の内容でポリシーを作成します。

カラム 設定値
サービス Elastic Container Service
アクション リスト
- ListAccountSettings
読み込み
- DescribeServices
- CreateService
- UpdateService
- RegisterTaskDefinition
リソース すべてのリソース
リクエスト条件 なし
名前 my-ecs-policy
任意です
説明 任意

次にグループから前回作成したグループを選択し、
アクセス許可のタブから今作ったポリシーをアタッチします。

Elastic IPの設定

最後に固定IPアドレスを取得します。
EC2インスタンスとか立て直しをするとIPアドレスが変わってしまうので、
EC2と固定IPアドレスの紐付けを行います。
Elastic IPの取得自体はVPCダッシュボードのElastic IP、
新しいアドレスの割り当てから取得出来ます。
Elastic IPはインスタンスに紐付いていないとお金が掛かるそうです。

取得したIPを選択して、アクション→アドレスの関連付けから行います。
インスタンスを選択すると自動で他の項目を埋めてくれると思います。
再関連付けもとりあえずチェックしておいて良いと思います。


これでAWS側の設定が終わりました。
長かった……。

CircleCIからpush

たぶん本題となるはずのcircleciです。。。
まず前回同様環境変数の追加を行います。
今回追加するのはクラスター名です。
それ以外の必要な変数は前回セットしています。

環境変数 概要
ECS_CLUSTER 作成したクラスター名


そして定義したジョブがこちらになります。
.circleci/config.yml

version: 2
jobs:
  # ~~ 略 ~~
  deploy:
    docker:
      - image: circleci/node:12-stretch
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Setup Enviroment
          command:
            echo 'export DOCKER_IMAGE=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${AWS_RESOURCE_NAME_PREFIX}:latest' >> $BASH_ENV
      - run:
          name: Install ecs-cli
          command: |
            sudo curl -o /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest
            sudo chmod +x /usr/local/bin/ecs-cli
      - run:
          name: Setup ecs-cli
          command: |
            ecs-cli configure --cluster ${ECS_CLUSTER} --default-launch-type EC2 --region ${AWS_DEFAULT_REGION} --config-name ecs-config
      # 初回はService定義がないため、Stop Serviceをコメントアウトする必要がある。。。
      - run:
          name: Stop Service
          command: |
            ecs-cli compose \
              --file .circleci/composition.yml \
              --project-name mu-web \
              --ecs-params .circleci/ecs-params.yml \
              --cluster-config ecs-config \
              service stop
      - run:
          name: Start Service
          command: |
            ecs-cli compose \
              --file .circleci/composition.yml \
              --project-name mu-web \
              --ecs-params .circleci/ecs-params.yml \
              --cluster-config ecs-config \
              service up

workflows:
  version: 2
  pipeline:
    jobs:
      - build
      - push:
          requires:
            - build
          filters:
            branches:
              only: master
      - deploy:
          requires:
            - push
          filters:
            branches:
              only: master

.circleci/composition.yml

version: "3"
services:
  mu-web-service:
    image: ${DOCKER_IMAGE}
    ports:
      - "80:80"

.circleci/ecs-params.yml

version: 1
task_definition:
  services:
    mu-web-service:
      mem_limit: 524288000 # 500MB
      mem_reservation: 262144000 # 250MB


ちょっと長いですが順々にステップを見ていきます。
最初はcircleciのいつものみたいな感じなので飛ばして、
まずは本筋とは離れるんですが環境変数DOCKER_IMAGEを用意してます。
別に用意しなくても良かったんですが、
後に参照する際に${DOCKER_IMAGE}って書きたかったのでセットしてます。
また$BASH_ENVに上書きすることで
後続のステップでもこの環境変数が参照できるようになります。
しないと、このステップだけ使える変数になってしまいます。

次は見たまんまCLIのインストール。
AWSのドキュメント通りに入れます。

インストールの後はCLIの設定の作成です。
使用するリージョンやクラスターを指定しています。
そして作った設定は後で使用するため、
--config-nameオプションで名前を付けています。

サービスが既に起動していたら、
既にポートが使われている状態になっていて更新出来ません。
そのためサービスの停止を入れています。
オプションは公式にあるんですが、
--project-nameはこの場合だと.circleciになっていて
familyが違うと怒られたので直接ディレクトリ名を指定しています。
他にも--fileでDocker構成ファイルを指定したり、
--ecs-paramsでDocker構成ファイルでは足りないECSのパラメータを指定してあげたりしています。
そして先ほど用意した設定名を渡しています。
ただ、コメントにも書いたとおり
初回はサービスが存在しないのでstopコマンドはそんなサービス無いよって行って失敗します。
なのでもっとしっかりやるなら、
シェルスクリプトを用意してそこで終了ステータスに応じてstopを走らせるかどうか場合分けする感じでしょうか。

最後にサービスを起動のコマンドを流します。
service upcreatestartの複合です。
こちらにも各オプションでファイルやパラメータ、設定を渡しています。


Docker構成ファイルはまんまdocker-compose.ymlだと思います。
そしてそこで環境変数が参照できるんで、
初めに用意した{DOCKER_IMAGE}を入れてみています。

ECSパラメータのファイルでは、
ECSで利用するタスク定義になっています。
設定しているのはハード/ソフトメモリ制限程度です。
IAMロールとかも設定できるんですが、
今回CLIユーザーにやらせているのでタスク実行ロールはなしです。


さて、初回にサービス停止失敗するという残念な点がありますが
これで自動pushできるようになりました。
長かった。



もっとこうしたほうが良いんじゃない?とかありましたら、
滅茶苦茶気軽にご教示頂けると幸いでっす!