Amazon Web Services ブログ

Amazon ECS on AWS Fargate を利用したコンテナイメージのビルド

この記事は Building container images on Amazon ECS on AWS Fargate を翻訳したものです。

コンテナイメージのビルドのプロセスは、アプリケーションのコード、ライブラリ、および依存関係を再利用可能なファイルシステムにパッケージ化することです。開発者は自分たちのコードと一緒に、コンテナイメージを組み立てるためのすべてのコマンドを含む Dockerfile を作成します。この Dockerfile は、Docker Engine に組み込まれているようなコンテナイメージのビルドツールを利用して、コンテナイメージを作成するために使用されます。ビルドされたコンテナイメージは、Amazon ECS や EKS のようなコンテナの実行環境でコンテナを作成するために使用されます。開発ワークフローの中では、開発者は例えばローカルの Docker Engine に対して docker build コマンドを実行して、コンテナイメージを各自のローカルマシン上でビルドします。

この方法はコードを書いてビルドする開発者が 1 人しかいない場合には有効ですが、スケーラブルなプロセスではありません。一般的な開発チームでは複数の開発者が同時にコードを修正することを考えると、1 人の開発者がコンテナイメージのビルドを担当することはできません。DevOps エンジニアは継続的デリバリー (CD) パイプラインを使用してこの問題を解決します。このパイプラインでは開発者は Git リポジトリなどの集中型のコードリポジトリにコードをチェックインし、JenkinsCodePipeline などのツールを用いてコンテナのビルドを自動化します。お客様からのフィードバックによると、多くの DevOps チームが CD パイプラインを管理するために Amazon Elastic Container Service (Amazon ECS) または Amazon Elastic Kubernetes Service (Amazon EKS) を選択していることがわかりました。ECS や EKS のようなコンテナオーケストレータは、CD システムに要求されるようなインフラストラクチャのスケーリングを簡素化します。例えば Jenkins を利用する場合ですが、Jenkins のパイプラインがトリガーされ、ビルドを実行するために追加のコンピュートキャパシティが必要になると ECS は EC2 インスタンスをオートスケールします。またお客様からは、サーバーの管理を不要にするために CD のワークロードを Fargate 上で運用したいとの声もいただいています。

コンテナ内でのコンテナイメージのビルド

ローカルマシン上でコンテナをビルドするのとは対照的に、ECS クラスターで稼働する Jenkins (または同様のツール) は実行中のコンテナ内でコンテナイメージをビルドします。ただし Docker Engine に含まれるような一般的なコンテナイメージのビルドツールは、実行中のコンテナのセキュリティ境界内では実行できません。Linux のシステムコール (seccomp 経由) および Linux のセキュリティモジュール (AppArmour または SELinux) へのアクセスが制限されているため、Docker Engine はコンテナ内で実行できません。

そのため、EC2 インスタンス上で従来の docker build 方式でコンテナイメージをビルドしたい場合には 2 つの選択肢があります。

  • ホスト上で動作している Docker Engine の Unix ソケットを実行中のコンテナにバインドマウントすることで、コンテナから基盤側の Docker API へのフルアクセスを可能にします。
  • 特権モードでコンテナを実行し、Linux システムコールとセキュリティモジュールの制限を取り除くことで Docker Engine がコンテナ内で実行できるようにします。

これら両方のアプローチには根本的なリスクがあります。ホストの Docker デーモンにアクセスしたり特権モードで実行したりするコンテナは、ホスト上で悪意のあるアクションを実行することもできます。例えばマウントされた Unix ソケットを介してホストの Docker Engine にアクセスしたコンテナは、基盤側の Docker API に完全にアクセスすることができます。これによりコンテナは、その Docker Engine 上で稼働している他のコンテナを起動・停止したり、他のコンテナに docker exec する権限を持つことになります。シングルテナントの ECS クラスターであっても、敵対的なアクターにバックドアを公開してしまうため、深刻な問題につながる可能性があります。

AWS Fargate は各コンテナを隔離された VM 環境で実行します。また AWS Fargate は、実行中のコンテナが基盤側のホストからディレクトリやソケットをマウントすることを禁止したり、コンテナが Linux のケイパビリティを使用して実行されるのを防いだり、--privileged フラグを使用して実行することを禁止するなど、セキュリティのベストプラクティスを実施しています。その結果、お客様は Docker Engine 内のビルドツールを使用して Fargate コンテナ内でコンテナイメージをビルドすることができません。

kaniko

ここ数年、特権モードを必要とせずにコンテナイメージをビルドしたいという問題を解決するために新しいツールが登場しています。kaniko はそのようなツールの一つで、従来の Docker と同じように Dockerfile からコンテナイメージをビルドします。しかし Dockerとは異なり、root 権限を必要とせず Dockerfile 内の各コマンドを完全にユーザー名前空間内で実行します。そのため、実行中のコンテナ内で root 権限無しでコンテナイメージをビルドできます。 kaniko はマルチテナントのコンテナクラスター内で動作するのを目的に設計された、優れたスタンドアロンのビルドツールです。

kaniko は Fargate が提供するようなコンテナ化された環境の制約の中で動作するように設計されており、Linux のケイパビリティや Linux セキュリティモジュールの無効化など、基盤側のホストへのアクセスを必要としません。そのため、AWS Fargate 上でコンテナイメージをビルドするための理想的なユーティリティです。
次のセクションでは、kaniko を使用して Fargate 上のコンテナでコンテナイメージをビルドする方法をご紹介します。

ソリューション

kaniko を使用して Amazon ECS on AWS Fargate 上でコンテナイメージをビルドするには以下が必要です。

  • AWS CLI バージョン 2
  • Docker
  • 既存の Amazon VPC とサブネット。このサブネットにデプロイされた ECS タスクは、アプリケーションのソースコードリポジトリへアクセスできる必要があります。これはパブリックな GitHub リポジトリでもよいのですが、その場合はサブネットからインターネットへアクセスが必要になります。

まず、使用する予定の VPC とサブネットの ID を環境変数に設定することから始めましょう。

export KANIKO_DEMO_VPC=<YOUR VPC ID e.g., vpc-055e876skjwshr1dca1>
export KANIKO_DEMO_SUBNET=<SUBNET ID e.g., subnet-09a9a548da32bbbbe>

ECS クラスターを作成します。

aws ecs create-cluster \
  --cluster-name kaniko-demo-cluster

デモアプリケーションを保存する ECR リポジトリを作成します。

export KANIKO_DEMO_REPO=$(aws ecr create-repository \
 --repository-name kaniko-demo \
 --query 'repository.repositoryUri' --output text)
export KANIKO_DEMO_IMAGE="${KANIKO_DEMO_REPO}:latest"

kaniko のコンテナイメージを保存する ECR リポジトリを作成します。

export KANIKO_BUILDER_REPO=$(aws ecr create-repository \
 --repository-name kaniko-builder \
 --query 'repository.repositoryUri' --output text)

export KANIKO_BUILDER_IMAGE="${KANIKO_BUILDER_REPO}:executor"

kaniko コミュニティが提供するアップストリームイメージは、お使いのコンテナリポジトリによってはうまくいく機能するかもしれませんが、このウォークスルーでは kaniko が Amazon ECR にプッシュできるように設定ファイルを渡す必要があります。ECR リポジトリにコンテナイメージをプッシュするために、ECR Credential Helper は AWS Credentials を使用して認証を行います。アップストリームの kaniko コンテナイメージには、ECR Credentials Helper のバイナリがすでに含まれています。ただし、ECR の認証に ECR Credential Helperを使用するように kaniko に指示するための設定ファイルが必要です。

# コンテナイメージのアーティファクトを保存するディレクトリの作成
mkdir kaniko
cd kaniko

# Dockerfile の作成
cat << EOF > Dockerfile
FROM gcr.io/kaniko-project/executor:latest
COPY ./config.json /kaniko/.docker/config.json
EOF

# レジストリ認証用の Kaniko 設定ファイルの作成
cat << EOF > config.json
{ "credsStore": "ecr-login" }
EOF

コンテナイメージをビルドし、ECR へプッシュします。

docker build --tag ${KANIKO_BUILDER_REPO}:executor .

aws ecr get-login-password | docker login \
   --username AWS \
   --password-stdin \
   $KANIKO_BUILDER_REPO
   
docker push ${KANIKO_BUILDER_REPO}:executor

ECS タスク用の IAM ロールを作成します。この IAM ロールは、デモアプリケーションのコンテナイメージを ECS タスクから ECR へプッシュするためのものです。

# 信頼ポリシーの作成
cat << EOF > ecs-trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ecs-tasks.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  }
EOF
 
# IAM ポリシーの作成
cat << EOF > iam-role-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload",
                "ecr:PutImage",
                "ecr:BatchGetImage",
                "ecr:BatchCheckLayerAvailability"
            ],
            "Resource": "*"
        }
    ]
}
EOF
 
# IAM ロールの作成
ECS_TASK_ROLE=$(aws iam create-role \
  --role-name kaniko_ecs_role \
  --assume-role-policy-document file://ecs-trust-policy.json \
  --query 'Role.Arn' --output text)
 
aws iam put-role-policy \
 --role-name kaniko_ecs_role \
 --policy-name kaniko_push_policy \
 --policy-document file://iam-role-policy.json

ECS のタスク定義を作成し、kaniko コンテナの実行方法、アプリケーションのソースコードリポジトリの場所、ビルドされたコンテナイメージをプッシュする場所を定義します。

# ログ出力のための Amazon CloudWatch ロググループの作成
aws logs create-log-group \
  --log-group-name kaniko-builder

# AWS アカウント ID のエクスポート
AWS_ACCOUNT_ID=$(aws sts get-caller-identity \
  --query 'Account' \
  --output text)

# ECS タスク定義の作成
cat << EOF > ecs-task-defintion.json
{
    "family": "kaniko-demo",
    "taskRoleArn": "$ECS_TASK_ROLE",
    "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
    "networkMode": "awsvpc",
    "containerDefinitions": [
        {
            "name": "kaniko",
            "image": "$KANIKO_BUILDER_IMAGE",
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "kaniko-builder",
                    "awslogs-region": "$(aws configure get region)",
                    "awslogs-stream-prefix": "kaniko"
                }
            },
            "command": [
                "--context", "git://github.com/ollypom/mysfits.git",
                "--context-sub-path", "./api",
                "--dockerfile", "Dockerfile.v3",
                "--destination", "$KANIKO_DEMO_IMAGE",
                "--force"
            ]
        }],
    "requiresCompatibilities": ["FARGATE"],
    "cpu": "512",
    "memory": "1024"
}
EOF

aws ecs register-task-definition \
  --cli-input-json file://ecs-task-defintion.json

ECS の run-task API を使用して、kaniko をスタンドアロンタスクとして実行します。この run-task API の実行は様々な CD や自動化ツールを使用して自動化することができます。サブネットがパブリックサブネットの場合は、"assignPublicIp" フィールドを "ENABLED" に設定します。

セキュリティグループを作成し、kaniko タスクを作成します。

# ECS タスク用のセキュリティグループの作成
KANIKO_SECURITY_GROUP=$(aws ec2 create-security-group \
  --description "SG for VPC Link" \
  --group-name KANIKO_DEMO_SG \
  --vpc-id $KANIKO_DEMO_VPC \
  --output text \
  --query 'GroupId')

# ECS タスクの開始
cat << EOF > ecs-run-task.json
{
    "cluster": "kaniko-demo-cluster",
    "count": 1,
    "launchType": "FARGATE",
    "networkConfiguration": {
        "awsvpcConfiguration": {
            "subnets": ["$KANIKO_DEMO_SUBNET"],
            "securityGroups": ["$KANIKO_SECURITY_GROUP"],
            "assignPublicIp": "DISABLED"
        }
    },
    "platformVersion": "1.4.0"
}
EOF

# "Run Task" コマンドを使用した ECS タスクの実行
aws ecs run-task \
    --task-definition kaniko-demo:1 \
    --cli-input-json file://ecs-run-task.json

タスクが開始されると、CloudWatch を使用して kaniko のログを見ることができます。

aws logs get-log-events \
  --log-group-name kaniko-builder \
  --log-stream-name $(aws logs describe-log-streams \
     --log-group-name kaniko-builder \
     --query 'logStreams[0].logStreamName' --output text) 

このタスクはソースコードからコンテナイメージを作成します。ECR にコンテナイメージをプッシュしてタスクは終了します。

ビルドされたコンテナイメージが正常にプッシュされたことを確認するために、ECRリポジトリのコンテナイメージを一覧表示します。

# kaniko-demo リポジトリ内のコンテナイメージを表示する
aws ecr list-images --repository-name kaniko-demo

# 成功したレスポンス例
{
    "imageIds": [
        {
            "imageDigest": "sha256:500200c2a854a0ead82c5c4280a3ced8774965678be20c117428a0119f124e33",
            "imageTag": "latest"
        }
    ]
}

後片付け

# 2 つの ECR リポジトリを削除
aws ecr delete-repository --repository-name kaniko-demo --force
aws ecr delete-repository --repository-name kaniko-builder --force

# ECS クラスターを削除
aws ecs delete-cluster --cluster kaniko-demo-cluster

# ECS タスク定義を削除
aws ecs deregister-task-definition --task-definition kaniko-demo:1

# CloudWatch ロググループの削除
aws logs delete-log-group --log-group-name kaniko-builder

# IAM Policy の削除
aws iam delete-role-policy --role-name kaniko_ecs_role --policy-name kaniko
_push_policy

# IAM Role の削除
aws iam delete-role --role-name kaniko_ecs_role

# セキュリティグループの削除
aws ec2 delete-security-group --group-id $KANIKO_SECURITY_GROUP

まとめ

AWS Fargate の強化されたセキュリティプロファイルのため、従来のコンテナイメージビルドツールを活用していたお客様はサーバーレスコンピュートを活用できず、CD パイプラインをサポートするためのサーバーのプロビジョニングと管理に追われていました。このブログ記事では kaniko のような最新のコンテナイメージビルドツールが、AWS Fargate 上で実行される Amazon ECS タスクにおいて追加の Linux 特権なしで実行できる方法を示しました。

  • kaniko が Amazon EKS 上の Jenkins パイプラインでどのように使用できるかについてはこのブログ記事を参照してください。
    kaniko の詳細については github リポジトリに追加のドキュメントがあります。

翻訳はソリューションアーキテクト加治が担当しました。原文はこちらです。