クラウド事業部の上野です。

AWSにあるコンテナサービスを使ってみたい!今後の弊社のサービスで活用できるかも!ついでにCIツールでデプロイまで自動化したい!
ということでECS(Amazon Elastic Container Service)とECR(Amazon Elastic Container Registry)で継続的デリバリー環境を作ってみました。
今回はCIツールとしてCircleCIを利用してみます。

簡単に各サービスを説明しますと、

CircleCIはCI/CD(継続的インテグレーション/継続的デリバリー)を行うサービスです。

ECSはDocker コンテナをサポートするAWSのコンテナオーケストレーションサービスです。

ECRはAWS完全マネージド型のDockerコンテナレジストリです。

これらのサービスを使って、GitHubにプッシュしたら自動的にDockerイメージをビルドし、最終的にECSのコンテナにデプロイされるという環境を作ってみたいと思います。

今回の構成としてはこのような形です。

f:id:seeds-std:20190911152413p:plain
構成図

では、環境を作っていきましょう。

ECRの作成

まずはDockerのコンテナを保管するECRを作成します。
AWSマネジメントコンソールよりECRのダッシュボードを開き、リポジトリ名を入力して作成します。
今回はお試しということでnginxのDockerイメージを使います。

ECRにリポジトリを作成すると”プッシュコマンドの表示”というボタンが押せるようになります。
これはDockerイメージの作成からECRへのプッシュまで、具体的にどういうコマンドを実行すればいいか教えてくています。
親切ですね、具体的には下記のコマンドになります。

$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker build -t seeds-test .
docker tag seeds-test:latest XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/seeds-test:latest
docker push XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/seeds-test:latest

上記コマンドを実行してECRにプッシュするとこのようになります。

f:id:seeds-std:20190911153941p:plain
ecr_push

ECSクラスター作成

ECRのリポジトリにDockerのイメージを準備できましたので、次はECSを準備していきます。
まずは土台となるECSクラスターを作成します。ECSクラスターとはコンテナインスタンスの集合体のことです。
コンテナインスタンスにはAWSがマネージドしてくれるAWS Fargateと自分自身で管理するEC2インスタンスの2種類がありますが、今回はAWSが管理してくれるFargateを利用します。
クラスター作成時にコンテナを動作させるVPCを新たに作成するか聞かれますが、今回は既存のVPCを使用するためVPCの新規作成は行わずにクラスター名だけ記入して作成します。

f:id:seeds-std:20190911155655p:plain
ecs-cluster

ECSタスク定義の作成

次はECSタスク定義を作成します。
ECSタスク定義とはアプリケーションの設計図です。どういったコンテナをどの程度のスペック(CPU、メモリ)で起動するかといった内容を定義します。
タスクの定義には起動タイプをFargateかEC2のいずれかを選択する必要があります。今回はFargateを選択します。

タスク定義名とタスクメモリとタスクCPUを指定し、それ以外はデフォルトのままにします。
設定の中段あたりにコンテナの定義という項目がありますので、そこで「コンテナの追加」ボタンを押してタスクで起動するコンテナの設定を行います。
コンテナ追加の画面でコンテナのイメージを選択する部分がありますので、ここでECRリポジトリに登録したイメージのURIを指定します。
ポートのマッピングは今回はnginxのコンテナですのでhttpの80番ポートをマッピングします。

f:id:seeds-std:20190911161504p:plain
ecs

これでタスク定義の作成は完了です。
タスク定義は今後リビジョンとして管理され、更新する度にリビジョンの数値が増えていきます。

ECSサービスの作成

ECSサービスとはECSクラスター上で起動させるタスクの数やAutoScalingの設定を管理します。
起動タイプはFARGATEを選択し、タスク定義とクラスターは事前に作成したものを指定します。
タスクの数の項目でサービス上で何個のタスクを起動させるかを指定できますので、今回はタスクの数を2に指定して、nginxのコンテナが2つ(タスクごとに1つのコンテナ)起動するようにします。

f:id:seeds-std:20190911165301p:plain
ecs-service

次にネットワーク構成を定義します。
ここでECSサービスが起動するVPCや利用されるセキュリティグループ、ロードバランサーを指定します。
VPCやセキュリティグループ、ロードバランサは事前に用意しておいたものを指定しています。

f:id:seeds-std:20190911165942p:plain
ecs-service
f:id:seeds-std:20190911165957p:plain
ecs-service

サービスを作成するとサービスで定義した内容でコンテナが起動してきます。

f:id:seeds-std:20190911173503p:plain
ecr-service

この状態でELBのDNS名にアクセスするとnginxのウェルカムページが表示され、コンテナが正常に稼働できていることを確認できます。

ここまででECRとECSの構築は完了です。

CircleCIとGitHubの設定

ここまでの作業でAWSを利用したコンテナサービスとしては稼働していますが、CircleCIとGitHubを使って継続的デリバリーな環境を作ります。
まず、CircleCIからECRとECSを操作するためのIAMユーザ(CircleCI用のアクセスキー)を作成します。ポリシーは下記のものを付与してください。作成時に表示されるアクセスキーとシークレットアクセスキーは後ほど利用しますのでメモしておいてください。

ユーザ名 circleci
ポリシー AmazonEC2ContainerRegistryFullAccess
     AmazonEC2ContainerServiceFullAccess

次にGitHubにDocker用のリポジトリを作成します。

f:id:seeds-std:20190911174844p:plain
github

GitHubでリポジトリが用意出来たらCircleCIにアクセスします。CircleCIではGitHubのアカウントを利用してサインアップできます。
GitHubのアカウントを利用してサインアップするとGitHubに用意したリポジトリが表示されていますのでFollowします。

f:id:seeds-std:20190911175327p:plain
circleci

これでGitHubとCircleCIの連携の準備ができました。

次にCircleCIのジョブの設定を行います。CircleCIのジョブの画面よりEnvironment Variablesを開き、環境変数をセットします。

  • AWS_ACCESS_KEY_ID(circleciユーザのアクセスキー)
  • AWS_SECRET_ACCESS_KEY(circleciユーザのシークレットアクセスキー)
  • AWS_ACCOUNT_ID(AWSのアカウント番号)
  • AWS_ECR_ACCOUNT_URL(ECRのリポジトリURL XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/seeds-test)
  • AWS_REGION(ap-northeast-1)

GitHubにプッシュされた場合にCircleCIの動作を制御するための設定ファイルを用意します。
.circleciというフォルダを作成し、その中にconfig.ymlというファイルを作成します。
config.ymlでCircleCIの動作を制御するのですが、ECRリポジトリへのアップロードやECSのタスク定義やサービスを更新するためのOrbs(※ジョブ、コマンドなどの設定要素をまとめた共有可能なパッケージのこと)をCircleCIが公式に提供しています。
これらを利用してGitHubにプッシュされた場合、DocerkイメージをビルドしてECRにアップロードし、アップロードされたイメージを元にECSのタスク定義とサービスを更新するといった内容のconfig.ymlを作成します。

circleci/aws-ecr@6.3.0

circleci/aws-ecs@0.0.11

config.ymlの内容

version: 2.1
orbs:
aws-ecr: circleci/aws-ecr@6.1.0
aws-ecs: circleci/aws-ecs@0.0.8
workflows:
build_and_push_image:
jobs:
- aws-ecr/build-and-push-image:
region: AWS_REGION
account-url: AWS_ECR_ACCOUNT_URL
repo: 'seeds-test' # GitHubのリポジトリ名
tag: "${CIRCLE_SHA1}"
- aws-ecs/deploy-service-update:
requires:
- aws-ecr/build-and-push-image
family: 'seeds-test-task' # ECSのタスク定義名
cluster-name: 'seeds-test-container' # ECSクラスター名
service-name: 'seeds-test-service' # ECSのサービス名
container-image-name-updates: 'container=seeds-test,tag=${CIRCLE_SHA1}' # タスク定義で指定しているコンテナ名

それではnginxのDocerfileと作成したconfig.ymlをGitHubにプッシュしてみましょう。

f:id:seeds-std:20190911193036p:plain
github

CircleCIをみるとプッシュを検知してジョブが動いていることを確認できます。

f:id:seeds-std:20190911193415p:plain
circleci

ECRの画面をみると新しいイメージが登録されていることを確認できます。

f:id:seeds-std:20190911193818p:plain
ecr

ECSのタスク定義も更新されています。

f:id:seeds-std:20190911194011p:plain
ecs

ECSのサービスで指定されるタスク定義も新しいものに更新され、自動でAutoScalingが実行されています。

f:id:seeds-std:20190911194141p:plain
ecs

これでCircleCI + GitHub + ECR + ECS で継続的デリバリー環境が構築できました。
今回構築した環境はとりあえず動く環境という状態を作りましたが、細かい設定をしていけばより柔軟な環境が作り上げることができます。例えばdevelopブランチにプッシュした場合は開発環境のECSにデプロイ、prodcutブランチにプッシュした場合は本番環境のECSにデプロイするといったことも可能です。

今回は案件の関係でCircleCIを利用する機会があったため、CIツールにCircleCIを利用しましたが、AWSにはもともとAWS CodeBuildやAWS CodePipelineなどのCIツールが用意されています。次回はこれらを使って継続的デリバリーの環境を作ってみたいと思います。