Amazon Web Services ブログ

Amazon EKS on Fargate を使用してコンテナイメージをビルドする方法

この記事は、How to build container images with Amazon EKS on Fargate を翻訳したものです。

本投稿は、Container Specialist Solutions Architect の Re Alvarez-Parmar により寄稿されました。

コンテナは、開発者がアプリケーションをパッケージ化、配布、そしてデプロイする方法を簡素化するのに役立ちます。開発者は、アプリケーションコード、ライブラリ、およびその他の依存関係を含むコンテナイメージにコードをパッケージ化します。このイメージを使用して、コンテナ化されたアプリケーションを互換性のある任意のオペレーティングシステムにデプロイすることができます。2013 年の Docker のリリース以来、コンテナの実行、イメージのビルドおよびリポジトリへのプッシュが容易に行えるようになりました。

ただし、Amazon ECS や Amazon EKS のような環境で Docker を使用してコンテナをビルドするには、Docker で Docker を実行する必要がありますが、これには重大な影響があります。おそらく、Docker を使用してコンテナ化された環境でコンテナイメージをビルドするための最も魅力的ではない前提条件は、特権モードでコンテナを実行する、という要件です。これは、セキュリティ意識の高い殆どの開発者が避けたい慣行です。Docker を使用してラップトップ上でイメージをビルドしても、セキュリティに深刻な影響はない場合があります。それでも、Kubernetes クラスターでコンテナに昇格された特権を与えることは避けるのが最善です。この厳しい要件により Fargate では特権コンテナが許可されていないため、Fargate で Docker と EKS を使用してコンテナイメージをビルドすることもできなくなります。

kaniko

特権モードを必要とせずにコンテナイメージをビルドする問題に対処するために、ここ数年で新しいツールが登場しました。kaniko は、Docker と同じように、Dockerfile からコンテナイメージをビルドするツールの 1 つです。ただし Docker とは異なり、Docker デーモンに依存せずに Dockerfile 内の各コマンドを完全にユーザースペースで実行します。したがって標準の Kubernetes クラスターや Fargate など、Docker デーモンを簡単または安全に実行できない環境でコンテナイメージをビルドすることができます。

Jenkins

DevOps チームは、継続的デリバリー (CD) ツールを使用してコンテナイメージのビルドを自動化します。AWS の利用者は、ソフトウェアのビルド、テスト、デプロイを自動化する AWS CodePipeline などのフルマネージドな継続的デリバリーサービスを使用できます。利用者は、Jenkins on Amazon EC2、Amazon ECS、または Amazon EKS のような自己管理ソリューションをデプロイすることもできます。

自己管理型の Jenkins クラスターを実行する多くの AWS の利用者は、ECS または EKS で実行することを選択します。EKS または ECS で Jenkins を実行している利用者は、Fargate を使用してサーバーを管理せずに Jenkins クラスターと Jenkins エージェントを実行することができます。Jenkins はデータの永続性を必要とするため、以前は Jenkins クラスターを実行するためには EC2 インスタンスが必要でした。Fargate は Amazon Elastic File System (EFS) と統合され、アプリケーションにストレージを提供できるようになりました。それによって、EKS と Fargate を使用して Jenkins コントローラーとエージェントを実行することもできます。

kaniko を使用してコンテナをビルドし、Jenkins を使用してビルドパイプラインをオーケストレーションすることで、EC2 インスタンスなしで CD インフラストラクチャ全体を運用することができます。

Fargate は自己管理型 CD インフラストラクチャにどのように役立てられるのか?

CD ワークロードはバースト性があります。業務時間中、開発者はコード変更をチェックインし、CD パイプラインが起動されることによって、CD システムへの需要が高まります。そのときに十分なコンピューティング能力が利用できない場合、パイプラインはリソースを求めて競合し、開発者は変更の影響を知るためにはさらに長く待たされることがあります。その結果、開発者の生産性が低下します。さらに業務時間外は、コストを削減するためにインフラストラクチャを縮小する必要があります。

パフォーマンスとコストを同時に実現するためにインフラストラクチャのキャパシティを最適化することは、DevOps エンジニアにとっては困難です。それに加えて、Kubernetes で自己管理型 CD インフラストラクチャを実行している DevOps チームは、ワーカーノードの管理、スケーリング、アップグレードも担当します。

Fargate を使用しているチームは、サーバーのメンテナンスに費やす時間が少ないため、ビジネス上の課題を解決するためにより多くの時間を費やすことができます。EKS on Fargate で CD インフラストラクチャを実行すると、DevOps チームの運用上の負担が軽減されます。サーバーや AMI を作成および管理する必要がないという明らかなメリットに加え、Fargate は DevOps チームが Kubernetes で CD ワークロードを以下の方法で簡単に操作できるようになります。

より簡単な Kubernetes データプレーンのスケーリング

コードの変更によってパイプラインが起動されるため、継続的デリバリー (CD) のワークロードは絶えず変動します。Fargate を使用すると、Pod が作成および終了されるとき Kubernetes データプレーンが自動的にスケーリングされます。つまり Kubernetes データプレーンは、ビルドパイプラインが起動されるとスケールアップし、ジョブが完了するとスケールダウンします。クラスターが完全に Fargate で実行されている場合は、Kubernetes Cluster Autoscaler を実行する必要はありません。

プロセスの分離性向上

厳密にコンピューティングリソースを分離しない共有クラスターでは、複数のコンテナが CPU、メモリ、ディスク、およびネットワークを巡って競合するため、リソースの競合が発生する可能性があります。Fargate は、VM が分離された環境で各 Pod を実行します ― 言い換えると、2 つの Pod が同じ VM を共有することはありません。その結果、同時並行する CD ワークストリームはコンピューティングリソースにおいて競合しません。

Kubernetes のアップグレードを簡素化

EKS のアップグレードは 2 つのステップで行われます。まず、EKS のコントロールプレーンをアップグレードします。完了したら、データプレーンと Kubernetes アドオンのアップグレードを行います。EC2 では、ノードを遮断し、Pod を退避させ、ノードを順番にアップグレードする必要がありますが、Fargate においてノードをアップグレードするには、その Pod をただ再起動させるだけです。

Pay per pod

Fargateでは、Pod のために確保した CPU やメモリに対して支払いを行います。CI パイプラインを実行するために EC2 インスタンスを使用した場合に発生するアイドル時間分を支払う必要がないため、AWS からの請求額を削減することができます。加えて Compute Savings Plan を利用することで、Fargate のコストをさらに削減することができます。

また Fargate は、PCI DSS レベル 1、ISO 9001、ISO 27001、ISO 27017、ISO 27018、SOC 1、SOC 2、SOC 3、および HIPAA に準拠をしています。Fargate のメリットについて詳しく知りたい方は、Massimo Re Ferre 氏の投稿「Saving money a pod at a time with EKS, Fargate, and AWS Compute Savings Plans」の参照をお勧めします。

ソリューション

Jenkins クラスターをホストする EKS クラスターを作成します。Jenkins は Fargate 上で実行され、Amazon EFS を使用して Jenkins の設定を永続化します。ネットワークロードバランサーが、Jenkins へのトラフィックを分散します。 Jenkins が動作可能になったら、kaniko を使用して Fargate 上にコンテナイメージをビルドするためのパイプラインを作成します。

チュートリアルを完了するには、以下のものが必要です。

いくつかの環境変数を設定することから始めましょう。

export JOF_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
export JOF_REGION="ap-southeast-1"
export JOF_EKS_CLUSTER=jenkins-on-fargate

EKS クラスターを作成する

eksctl を使用して Fargate を有効にしたEKS クラスターを作成します。このクラスターは EC2 インスタンス上には存在しません。

eksctl create cluster \
  --name $JOF_EKS_CLUSTER \
  --region $JOF_REGION \
  --version 1.18 \
  --fargate

--fargate オプションを指定すると、eksctl は Pod 実行ロールと Fargate プロファイルを作成し、coredns の Deployment が Fargate 上で実行できるようにパッチを適用します。

環境の準備

Jenkins を実行するために使用するコンテナイメージは、コンテナの /var/jenkins_home パスの下にデータを保存します。ここでは Amazon EFS を使用して、Jenkins pod に永続ボリュームとしてマウントできるファイルシステムを作成します。この永続ボリュームは、Jenkins pod が終了したり再起動したりしても、データの損失を防ぐことができます。

スクリプトをダウンロードして環境を準備します。

curl -O https://raw.githubusercontent.com/aws-samples/containers-blog-maelstrom/main/EFS-Jenkins/create-env.sh
chmod +x create-env.sh
. ./create-env.sh

このスクリプトは、

  • EFS ファイルシステム、EFS マウントポイント、EFS アクセスポイント、セキュリティグループを作成します。
  • EFS-backed ストレージクラス、永続ボリューム、および PersistentVolumeClaim を作成します。
  • AWS Load Balancer Controller のデプロイを行います。

Jenkins のインストール

ロードバランサーと永続ストレージの設定が完了し、Jenkins をインストールする準備が整いました。

Helm を使って、EKS クラスターに Jenkins をインストールします。

helm repo add jenkins https://charts.jenkins.io && helm repo update &>/dev/null

helm install jenkins jenkins/jenkins \
  --set rbac.create=true \
  --set controller.servicePort=80 \
  --set controller.serviceType=ClusterIP \
  --set persistence.existingClaim=jenkins-efs-claim \
  --set controller.resources.requests.cpu=2000m \
  --set controller.resources.requests.memory=4096Mi \
  --set controller.serviceAnnotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"=nlb-ip

Jenkins の Helm チャートでは、1 つのレプリカを持つ statefulset を作成し、Pod には 2 つの vCPU と 4 GB のメモリを持ちます。また Jenkins は 、コンテナの /var/jenkins_home パスにデータと設定を保存します。このパスは、この記事の前半で Jenkins 用に作成した EFS ファイルシステムにマッピングされています。

ロードバランサーの DNS 名を取得します。

printf $(kubectl get service jenkins -o \
  jsonpath="{.status.loadBalancer.ingress[].hostname}");echo

ロードバランサーの DNS 名をコピーしてからブラウザに貼り付けて、Jenkins ダッシュボードに移動します。ユーザー名 admin でログインします。Kubernetes の secret から admin ユーザーのパスワードを取得します。

printf $(kubectl get secret jenkins -o \
  jsonpath="{.data.jenkins-admin-password}" \
  | base64 --decode);echo

イメージのビルド

Jenkins をセットアップして、kaniko を使用してコンテナイメージをビルドするステップを含むパイプラインを作成します。

このデモで使用する Jenkins エージェント、kaniko executor、およびサンプルアプリケーションのコンテナイメージを保存するために使用される 3 つの Amazon Elastic Container Registry (ECR) リポジトリを作成します。

JOF_JENKINS_AGENT_REPOSITORY=$(aws ecr create-repository \
  --repository-name jenkins \
  --region $JOF_REGION \
  --query 'repository.repositoryUri' \
  --output text)

JOF_KANIKO_REPOSITORY=$(aws ecr create-repository \
  --repository-name kaniko \
  --region $JOF_REGION \
  --query 'repository.repositoryUri' \
  --output text)

JOF_MYSFITS_REPOSITORY=$(aws ecr create-repository \
  --repository-name mysfits \
  --region $JOF_REGION \
  --query 'repository.repositoryUri' \
  --output text)

Jenkins エージェントのコンテナイメージを準備します。

aws ecr get-login-password \
  --region $JOF_REGION | \
  docker login \
    --username AWS \
    --password-stdin $JOF_JENKINS_AGENT_REPOSITORY

docker pull jenkins/inbound-agent:4.3-4-alpine
docker tag docker.io/jenkins/inbound-agent:4.3-4-alpine $JOF_JENKINS_AGENT_REPOSITORY
docker push $JOF_JENKINS_AGENT_REPOSITORY

kaniko コンテナイメージを準備します。

mkdir kaniko
cd kaniko

cat > Dockerfile<<EOF
FROM gcr.io/kaniko-project/executor:debug
COPY ./config.json /kaniko/.docker/config.json
EOF

cat > config.json<<EOF
{ "credsStore": "ecr-login" }
EOF

docker build -t $JOF_KANIKO_REPOSITORY .
docker push $JOF_KANIKO_REPOSITORY

Jenkins サービスアカウントの IAM ロールを作成します。このロールにより、Jenkins エージェント Pod は ECR との間でイメージをプッシュおよびプルできます。

eksctl create iamserviceaccount \
    --attach-policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser \
    --cluster $JOF_EKS_CLUSTER \
    --name jenkins-sa-agent \
    --namespace default \
    --override-existing-serviceaccounts \
    --region $JOF_REGION \
    --approve

UI で新しいジョブを作成します。

ジョブに名前を付けて、新しいパイプラインを作成します。

CLI に戻り、パイプライン構成でファイルを作成します。

cat > kaniko-demo-pipeline.json <<EOF
pipeline {
    agent {
        kubernetes {
            label 'kaniko'
            yaml """
apiVersion: v1
kind: Pod
metadata:
  name: kaniko
spec:
  serviceAccountName: jenkins-sa-agent
  containers:
  - name: jnlp
    image: '$(echo $JOF_JENKINS_AGENT_REPOSITORY):latest'
    args: ['\\\$(JENKINS_SECRET)', '\\\$(JENKINS_NAME)']
  - name: kaniko
    image: $(echo $JOF_KANIKO_REPOSITORY):latest
    imagePullPolicy: Always
    command:
    - /busybox/cat
    tty: true
  restartPolicy: Never
"""
        }
    }
    stages {
        stage('Make Image') {
            environment {
                DOCKERFILE  = "Dockerfile.v3"
                GITREPO     = "git://github.com/ollypom/mysfits.git"
                CONTEXT     = "./api"
                REGISTRY    = '$(echo ${JOF_MYSFITS_REPOSITORY%/*})'
                IMAGE       = 'mysfits'
                TAG         = 'latest'
            }
            steps {
                container(name: 'kaniko', shell: '/busybox/sh') {
                    sh '''#!/busybox/sh
                    /kaniko/executor \\
                    --context=\${GITREPO} \\
                    --context-sub-path=\${CONTEXT} \\
                    --dockerfile=\${DOCKERFILE} \\
                    --destination=\${REGISTRY}/\${IMAGE}:\${TAG}
                    '''
                }
            }
        }
    }
}
EOF

kaniko-demo-pipeline.json の内容をコピーして、Jenkins の Pipeline Script セクションに貼り付けます。次のようになります。

Build Now の項目をクリックして、ビルドを開始します。

ビルドを開始すると、Jenkins が別の Pod を作成したことがわかります。パイプラインは Jenkins 用の Kubernetes プラグインを使用して、Kubernetes で動的な Jenkins エージェントを実行します。この Pod の kaniko executor コンテナは、サンプルコードリポジトリからコードのクローンを作成し、プロジェクトの Dockerfile を使用してコンテナイメージをビルドし、ビルドされたイメージを ECR にプッシュします。

kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
jenkins-0            2/2     Running   0          4m
kaniko-wb2pr-ncc61   0/2     Pending   0          2s

Jenkins にて build を選択し、Console Output に移動すると、ビルドを確認できます。

ビルドが完了したら AWS CLI に戻り、ビルドされたコンテナイメージがサンプルアプリケーションの ECR リポジトリにプッシュされていることを確認します。

aws ecr describe-images \
  --repository-name mysfits \
  --region $JOF_REGION

上記のコマンドの出力では、「mysfits」リポジトリに新しいイメージが表示されているはずです。

クリーンアップ

helm delete jenkins
helm delete aws-load-balancer-controller --namespace kube-system
aws efs delete-access-point --access-point-id $(aws efs describe-access-points --file-system-id $JOF_EFS_FS_ID --region $JOF_REGION --query 'AccessPoints[0].AccessPointId' --output text) --region $JOF_REGION
for mount_target in $(aws efs describe-mount-targets --file-system-id $JOF_EFS_FS_ID --region $JOF_REGION --query 'MountTargets[].MountTargetId' --output text); do aws efs delete-mount-target --mount-target-id $mount_target --region $JOF_REGION; done
sleep 5
aws efs delete-file-system --file-system-id $JOF_EFS_FS_ID --region $JOF_REGION
aws ec2 delete-security-group --group-id $JOF_EFS_SG_ID --region $JOF_REGION
eksctl delete cluster $JOF_EKS_CLUSTER --region $JOF_REGION
aws ecr delete-repository --repository-name jenkins --force --region $JOF_REGION
aws ecr delete-repository --repository-name mysfits --force --region $JOF_REGION
aws ecr delete-repository --repository-name kaniko --force --region $JOF_REGION

まとめ

EKS on Fargate を使えば、サーバー、AMI、ワーカーノードを管理することなく、継続的デリバリーの実行を自動化することができます。Fargate は、アプリケーションのスケールインとスケールアウトに応じて、Kubernetes のデータプレーンをオートスケールします。自動化されたソフトウェアビルドの場合、EKS on Fargate はパイプラインがビルドを起動すると自動スケールし、各ビルドが必要な容量を確保します。この記事では Jenkins を Fargate 上で完全にクラスター化し、--privileged モードを必要とせずにコンテナイメージのビルドを実行する方法を示しました。

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