Amazon Web Services ブログ

Docker Hub による AWS Container Services の認証

本投稿は Nathan Arnold による記事 Authenticating with Docker Hub for AWS Container Services を翻訳したものです。

Docker Hub は最近利用規約を更新し、コンテナイメージ Pull の rate limits を導入しました。これらの制限は Pro や Team プランのアカウントには適用されませんが、匿名ユーザーは IP アドレスごとに6時間あたり 100 Pull まで、認証済みの無料アカウントは6時間あたり200 Pull までと制限されています。この記事では、新たに設けられた制限による運用の混乱を回避し、プライベートコンテナイメージへのアクセスを制御するために、Amazon ECSAmazon EKS の両方を使用してプライベートリポジトリからイメージを Pull するために Docker Hub で認証する方法を学びます。まだ Docker Hub を使用していない場合は、AWSクラウド環境とネイティブに統合されたフルマネージドの代替手段としてAmazon Elastic Container Registry (Amazon ECR) を検討してみてはいかがでしょうか。

Amazon ECS による Docker Hub 認証

Amazon Elastic Container Service (Amazon ECS) は、フルマネージドなコンテナオーケストレーションサービスで、アプリケーションの一部として実行したいコンテナイメージをタスク定義と呼ばれるリソースで指定することができます。Docker Hub のユーザー名とパスワードをシークレットとして AWS Secrets Manager に保存し、AWS Key Management Service (AWS KMS) との連携を利用して、AWS KMSのカスタマーマスターキー(CMK)で保護された固有のデータキーでシークレットを暗号化することができます。そして、タスク定義でシークレットを参照し、AWS Identity and Access Management (IAM)でタスク実行ロールを作成することで、シークレットの取得と復号を行うための適切な権限を割り当てることができます。

ソリューションの概要

以下の図は、本記事で取り上げた Amazon ECS を使用して Docker Hub による認証を行うためのソリューションの概念図です。

この投稿の後続の手順に従い、以下のリソースを作成します。

  1. シークレットを暗号化するための AWS KMS のカスタマーマスターキーとエイリアス
  2. Docker Hub のユーザー名とパスワードを保存するための AWS Secrets Manager のシークレット
  3. シークレットを復号および取得する権限をタスクに与える ECS タスク実行ロール
  4. Amazon ECS CLI を使用して作成する ECS クラスターと VPC リソース
  5. AWS Fargate 起動タイプを使用してクラスター上でタスクの1つのインスタンスを実行する Amazon ECS の Service

前提条件:

このソリューションでは、次の前提条件が必要です。

イメージをプライベート Docker Hub リポジトリにプッシュ (オプション)

この投稿の構成に従う場合は、NGINX の公式 Docker ビルドを Pullし、プライベートリポジトリの名前でイメージにタグを付けて、Docker Hub アカウントに Push できます。<USER_NAME> 変数を Docker Hub ユーザー名に、<REPO_NAME> 変数をプライベートリポジトリの名前に、<TAG_NAME> 変数を使用したタグに置き換えます。

docker Pull nginx
docker tag nginx:latest <USER_NAME>/<REPO_NAME>:<TAG_NAME>
docker push <USER_NAME>/<REPO_NAME>:<TAG_NAME>

それ以外の場合は、選択した Docker イメージを自由に使用してください。ただし、この投稿で使用されているコマンドと構成に若干の変更を加える必要がある場合があることに注意してください。

AWS KMS CMK とエイリアスの作成

AWS CLI を使用して、AWS KMS でカスタマーマスターキー (CMK) とエイリアスを作成することから始めます。この CMK は、AWS Secrets Manager によって利用され、個々のシークレットの暗号化に使用される一意のデータキーに対してエンベロープ暗号化を実行します。エイリアスは CMK の表示名として機能し、キー ID よりも覚えやすいです。エイリアスは、アプリケーションの簡素化にも役立ちます。たとえば、コードでエイリアスを使用する場合、指定されたエイリアスを別の CMK に関連付けることで、コードが使用する基になる CMK を変更できます。

aws kms create-key --query KeyMetadata.Arn --output text

新しく作成されたキーの Amazon リソース名 (ARN) は、前のコマンドの出力として表示されます。 <CMK_ARN> 変数をその ARN に置き換え、<CMK_ALIAS> 変数を使用するエイリアスに置き換えます。

aws kms create-alias --alias-name alias/<CMK_ALIAS> --target-key-id <CMK_ARN>

次のステップで信頼ポリシードキュメントを作成するときにも、CMK の ARN が必要になります。

AWS Secrets Manager でシークレットの作成

この時点で、AWS Secrets Manager でシークレットを作成して、Docker Hubのユーザー名とパスワードを安全に保存できます。<USER_NAME> 変数を Docker Hub ユーザー名に、<PASSWORD> 変数を Docker Hub パスワードに、<CMK_ALIAS> 変数を前の手順の CMK のエイリアスに置き換えます。また、シークレットに階層的な名前を付けて、管理しやすくすることをお勧めします。次のコマンドのシークレット名には、dev/ プレフィックスが付いていることに注意してください。これにより、シークレットが仮想 dev フォルダーに保存されます。

aws Secrets Manager create-secret \
    --name dev/Docker HubSecret \
    --description "Docker Hub Secret" \
    --kms-key-id alias/<CMK_ALIAS> \
    --secret-string '{"username":"<USER_NAME>","password":"<PASSWORD>"}'

シークレットの ARN は、前のコマンドの出力として表示されます。次のステップで信頼ポリシードキュメントを作成するときに、この ARN を参照する必要があります。

IAM でタスク実行ロールの作成

最初に、役割を引き受けることができるプリンシパル (この場合はECSタスク) を指定するための信頼ポリシードキュメントを作成する必要があります。

cat << EOF > ecs-trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF

次に、ECS タスクが AWS Secrets Manager で作成されたシークレットを復号するおよび取得できるようにするパーミッションポリシードキュメントを作成します。 <SECRET_ARN> 変数と <CMK_ARN> 変数を、前の手順で作成したシークレットと CMK の ARN に置き換えます。

cat << EOF > ecs-secret-permission.json 
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "Secrets Manager:GetSecretValue"
      ],
      "Resource": [
        "<SECRET_ARN>",
        "<CMK_ARN>"     
      ]
    }
  ]
}
EOF

AWS CLI を使用して ECS タスク実行ロールを作成できるようになりました。前の手順で作成した信頼ポリシードキュメントを参照していることに注意してください。ファイルを正しく見つけるために、必要に応じてディレクトリパスを変更します。

aws iam create-role \
    --role-name ecsTaskExecutionRole \
    --assume-role-policy-document file://ecs-trust-policy.json

Amazon ECS タスクの実行に必要な他のAWSサービスリソース に基本的なパーミッションを追加するには、AWS 管理の ECS タスク実行ポリシーを新しく作成されたロールにアタッチします。

aws iam attach-role-policy \
    --role-name ecsTaskExecutionRole \
    --policy-arn arn:aws:iam::aws:policy/service-role/Amazon ECSTaskExecutionRolePolicy

最後に、タスクが AWS Secrets Manager から Docker Hub のユーザー名とパスワードを取得できるようにするインラインパーミッションポリシーを追加します。前の手順で作成したパーミッションポリシードキュメントを参照していることに注意してください。ファイルを正しく見つけるために、必要に応じてディレクトリパスを変更します。

aws iam put-role-policy \
    --role-name ecsTaskExecutionRole \
    --policy-name ECS-Secrets Manager-Permission \
    --policy-document file://ecs-secret-permission.json

ECS CLI の構成 (オプション)

Amazon ECS コマンドラインインターフェース (ESC CLI) は、Amazon ECS クラスターとそのセットアップに必要な AWS リソースの作成を簡素化する高レベルのコマンドを提供します。ECS CLI をインストールした後、オプションで ecs-cli configure profile コマンドを使用して、名前付き ECS プロファイルで AWS 認証情報を設定できます。プロファイルは 〜/.ecs/credentials ファイルに保存されます。

ecs-cli configure profile \
    --access-key <AWS_ACCESS_KEY_ID> \
    --secret-key <AWS_SECRET_ACCESS_KEY> \
    --profile-name <PROFILE_NAME>

ecs-cli configure profile default コマンドを使用して、デフォルトで使用するプロファイルを指定することもできます。 ECS プロファイルを設定したり、環境変数を設定したりしない場合は、〜/.aws/credentials ファイルに保存されているデフォルトの AWS プロファイルが使用されます。

さらに、ecs-cli configure コマンドを使用して、ECS クラスター名、デフォルトの起動タイプ、および ECS CLI で使用する AWS リージョンを設定できます。 <LAUNCH_TYPE> 変数は、FARGATE または EC2 のいずれかに設定できます。

ecs-cli configure \
    --cluster <CLUSTER_NAME> \
    --default-launch-type <LAUNCH_TYPE> \
    --config-name <CONFIG_NAME> \
    --region <AWS_REGION>

これらの値は、以降の手順でも、コマンドフラグを使って指定、上書きすることができます。

Amazon ECS クラスターの作成

ecs-cli up コマンドを使用して、クラスター名、AWSリージョン (us-east-1等) 、および起動タイプとして FARGATE を指定し、Amazon ECS クラスターを作成します。

ecs-cli up \
    --cluster <CLUSTER_NAME> \
    --region us-east-1 \
    --launch-type FARGATE \

FARGATE 起動タイプを使用することで、AWS Fargate がお客様に代わってコンピュートリソースを管理するため、お客様は独自のEC2コンテナインスタンスをプロビジョニングする必要がありません。デフォルトでは、ECS CLI はAWS CloudFormation スタックも起動して、アタッチされたインターネットゲートウェイ、2つのパブリックサブネット、およびセキュリティグループを備えた新しい VPC を作成します。上記のコマンドでフラグオプションを使用して、独自のリソースを作成することもできます。

セキュリティグループの構成

ECS クラスターが正常に作成されると、ターミナルに VPC とサブネット ID が表示されます。
次に、新しく作成されたセキュリティグループの JSON 記述を取得し、セキュリティグループID または GroupId をメモします。 <VPC_ID> 変数を新しく作成された VPC の ID に置き換えます。

aws ec2 describe-security-groups \
    --filters Name=vpc-id,Values=<VPC_ID> \
    --region us-east-1

任意の IPv4 アドレスからの HTTPトラフィック を許可するインバウンドルールをセキュリティグループに追加します。 <SG_ID> 変数を、前の手順で取得した GroupId に置き換えます。このインバウンドルールを使用すると、タスクで NGINX サーバーが実行されていること、およびプライベートイメージが Docker Hub から正常に Pull されたことを検証できます。

aws ec2 authorize-security-group-ingress \
    --group-id <SG_ID> \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0 \
    --region us-east-1

Amazon ECS の Service を 作成

Amazon ECS の Service を利用すると、タスク定義の複数のインスタンスを同時に実行および維持できます。ECS CLI を使用すると、Docker compose ファイルを使用して Service を作成できます。次の docker-compose.yml ファイルを作成します。このファイルは、 ウェブサーバーへのインバウンドトラフィック用にポート 80 を公開する web コンテナを定義します。以前にプライベート Docker Hub リポジトリにプッシュされた NGINX イメージを参照するには、<USER_NAME> 変数を Docker Hub ユーザー名に、<REPO_NAME> 変数をプライベートリポジトリの名前に、<TAG_NAME> 変数を使用したタグに置き換えます。

cat << EOF > docker-compose.yml
version: "3"
services:
    web:
        image: <USER_NAME>/<REPO_NAME>:<TAG_NAME>
        ports:
            - 80:80
EOF

また、この Service に、Amazon ECS 固有の追加パラメーターを指定するには、次の ecs-params.yml ファイルを作成する必要があります。 以下の services フィールドは、上記の Docker Compose ファイルの services フィールドに対応し、実行するコンテナの名前と一致することに注意してください。 ECS CLI が作成ファイルからタスク定義を作成すると、web service のフィールドが ECS コンテナ定義にマージされます。これには、使用するコンテナイメージと、それにアクセスするために必要なDocker Hub リポジトリの資格情報が含まれます。 <SECRET_ARN> 変数を、前に作成した AWS Secrets Manager シークレットの ARN に置き換えます。 <SUB_1_ID> , <SUB_2_ID> および <SG_ID> 変数を、ECS クラスターで作成された2つのパブリックサブネットとセキュリティグループの ID に置き換えます。

cat << EOF > ecs-params.yml
version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
  services:
    web:
        repository_credentials: 
            credentials_parameter: "<SECRET_ARN>"
run_params:credentials_parameter


  network_configuration:
    awsvpc_configuration:
      subnets:
        - "<SUB_1_ID>"
        - "<SUB_2_ID>"
      security_groups:
        - "<SG_ID>"
      assign_public_ip: ENABLED
EOF

次に、ecs-cli compose service up コマンドを使用して、compose ファイルから ECS の Service を作成します。 このコマンドは、現在のディレクトリで docker-compose.ymlecs-params.yml を検索します。 <CLUSTER_NAME> 変数を ECS クラスターの名前に置き換え、<PROJECT_NAME> 変数を ECS Service の目的の名前に置き換えます。

ecs-cli compose \
    --project-name <PROJECT_NAME> \
    --cluster <CLUSTER_NAME> \
    service up \
    --launch-type FARGATE

これで、ecs-cli compose service ps コマンドを使用して、Service で実行されている web コンテナを表示できます。

ecs-cli compose \
    --project-name <PROJECT_NAME> \
    --cluster <CLUSTER_NAME> \
    service ps

リストされているIPアドレスの80番ポートに移動すると、デフォルトの NGINX ウェルカムページが表示され、認証用の認証情報を使用して、タスクがプライベート Docker Hub リポジトリからコンテナイメージを正常に Pull できたことを確認できます。

クリーンアップ

Service の Desired Count を 0 に更新してから、ecs-cli compose service down コマンドを使用して Service を削除します。

ecs-cli compose \
    --project-name <PROJECT_NAME> \
    --cluster <CLUSTER_NAME> \
    service down

ecs-cli up で作成した AWS CloudFormation スタックと、関連するリソースを ecs-cli down コマンドで削除します。

ecs-cli down --cluster <CLUSTER_NAME>

Amazon EKS を使用した Docker Hub 認証

Amazon Elastic Kubernetes Service (Amazon EKS) は、Kubernetes コントロールプレーンまたはノードをインストール、操作、および維持することなく、AWS で Kubernetes を実行できるようにするマネージドサービスです。 Kubernetes は、コンテナ化されたアプリケーションのデプロイ、スケーリング、管理を自動化するためのオープンソースシステムです。

Docker Hub のユーザー名とパスワードを etcd に保存された Kubernetes secret として保存できます。これは、すべてのクラスターデータに使用される高可用性キーバリューストアであり、AWS Key Management Service (AWS KMS) との統合を活用して、独自のカスタマーマスターキー (CMK) を使用して、その Secret に対してエンベロープ暗号化を実行できます。シークレットが Kubernetes Secrets API を使用して保存される場合、Kubernetes が生成したデータ暗号化キー(DEK) は CMK を使用してさらに暗号化されます。次に、シークレットを参照する Service Account を作成し、その Service AccountDeployment の一部として起動する pod に関連付けて、kubelet ノードエージェントが pod に代わって Docker Hub からプライベートイメージを Pull できるようにします。

ソリューションの概要

以下の図は、Amazon EKS を使用して Docker Hub で認証するためにこの投稿で取り上げたソリューションの概要を示しています。

この投稿の後続の手順に従い、以下のリソースを作成します。

  1. ワーカーノードの マネージド型ノードグループ を持つ Amazon EKS クラスター
  2. 暗号化されてetcdに保存される Docker Registry secret
  3. pod で実行されているプロセスの ID として機能し、ImagePullSecret を参照する Service account
  4. Service Accountが関連付けられている pod の ReplicaSet を宣言的に指定する Deployment
  5. Elastic Load Balancer の DNS名で pod を公開する LoadBalancer service

前提条件

前のセクションで概説した前提条件に加えて、次のものも必要になります。

  • EKS クラスターを作成するための eksctl コマンドラインインターフェイスツール
  • EKS クラスター内で Kubernetes オブジェクト を作成および管理するための kubectl コマンドラインインターフェースツール

このソリューションの目的のために、前のセクションでプライベートリポジトリにプッシュされた NGINX の公式 Docker ビルドを引き続き使用できます。 それ以外の場合は、選択した Docker イメージを自由に使用してください。ただし、この投稿で使用されているコマンドと構成に若干の変更を加える必要がある場合があることに注意してください。

また、Kubernetes secret に対して エンベロープ暗号化 を実行するには、AWS KMS でエイリアスが関連付けられたカスタマーマスターキー (CMK) が必要です。 前のセクションで作成したCMKを引き続き使用することも、新しいCMKを作成することもできます。

Amazon EKS クラスターの作成

開始するには、Amazon EKS の公式 CLI である eksctl で使用する構成ファイルを作成します。 こデフォルトのパラメーターを使いたくない場合に、そのパラメーターを上書きするためにeksctlが使用する設定ファイルです。

このファイルは、クラスター名とリージョン (us-east-1) を指定するだけでなく、クラスターのワーカーノードとして機能する Amazon EC2 インスタンスのプロビジョニングとライフサイクル管理を自動化する Managed node group も指定することに注意してください。 これらの管理対象ノードは、Amazon EKS によって管理される Amazon EC2 AutoScaling グループの一部としてプロビジョニングされます。

AWS KMS で作成した CMK の ARN も参照され、EKS コントロールプレーンの Kubernetes API サーバーによって生成されたデータ暗号化キー (DEK) を暗号化するために使用されます。

cat << EOF > eks-dev-cluster.yaml 
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
    name: eks-dev
    region: us-east-1

managedNodeGroups: 
- name: eks-dev-nodegroup 
  desiredCapacity: 2
            
# KMS CMK for the EKS cluster to use when encrypting your Kubernetes secrets
secretsEncryption:
    keyARN: <CMK_ARN>
EOF

次のコマンドで <CMK_ALIAS> を指定すると、CMK の ARN (CMK_ARN) を取得できます。

aws kms describe-key --key-id alias/<CMK_ALIAS> | grep Arn

次に、eksctl create cluster コマンドを使用して、設定ファイルの仕様に従って、Amazon EKS で Kubernetes クラスターの作成を開始します。

eksctl create cluster -f eks-dev-cluster.yaml

このコマンドは、内部で AWS CloudFormation スタックを起動し、公式の Amazon EKS AMI を使用して、フルマネージド EKS コントロールプレーン、専用 VPC、および2つの Amazon EC2 ワーカーノードを作成します。

新しい Namespace の作成

pod 間の相互作用をより適切に管理するために、アプリケーションを kube-system または default 以外のネームスペースにデプロイすることが一般的にベストプラクティスと考えられているため、Kubernetes コマンドラインツール kubectl を使用してクラスター内に ネームスペース dev を作成します。

kubectl create ns dev

Docker レジストリシークレットの作成

次に、docker-registry タイプの Secret を作成し、<USER_NAME><PASSWORD>、および <EMAIL> 変数を Docker Hub の認証情報に置き換えます。

kubectl create secret docker-registry docker-secret \
    --docker-server="https://index.docker.io/v1/" \
    --docker-username="<USER_NAME>" \
    --docker-password="<PASSWORD>" \
    --docker-email="<EMAIL>" \
    --namespace="dev"

このシークレットを作成すると、EKS コントロールプレーンの Kubernetes API サーバーがローカルでデータ暗号化キー (DEK) を生成し、それを使用してシークレットのプレーンテキストペイロードを暗号化します。 次に、KubernetesAPI サーバーは AWS KMS を呼び出して、上記のクラスター構成ファイルで参照されている CMK で DEK を暗号化し、DEK で暗号化されたシークレットを etcd に保存します。 pod がシークレットを使用する場合、API サーバーは etcd から暗号化されたシークレットを読み取り、DEK を使用してシークレットを復号します。
次のコマンドを使用して、シークレットが作成されたことを確認します。

kubectl get secrets docker-secret --namespace=dev

Service Account の作成

次に、同じネームスペース dev に Service Account を作成して、pod で実行されるプロセスのIDを提供します。

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
    name: dev-sa
    namespace: dev
imagePullSecrets:
- name: docker-secret
EOF

imagePullSecrets フィールドは、Docker レジストリシークレットを kubelet ノードエージェントに渡すために使用されます。kubeletノードエージェントは、この情報を使用して、pod に代わって Docker HubからプライベートイメージをPullします。

次のコマンドを使用して、 Service Account の作成を確認します。

kubectl get sa dev-sa --namespace=dev

Deploymentの作成

次に、Deployment の詳細を指定する構成ファイルを作成します。これにより、3つのレプリケートされた pod が作成され、それぞれがプライベートDocker Hubリポジトリに格納されている NGINX イメージから構築されたコンテナを実行します。 上記で作成された Service account は、pod テンプレート仕様の一部としても参照されることに注意してください。 コンテナイメージの場合、<USER_NAME> 変数を Docker Hub ユーザー名に、<REPO_NAME> 変数をプライベートリポジトリの名前に、<TAG_NAME> 変数を使用したタグに置き換えます。 imagePullPolicy は、ローカルにキャッシュされたコピーを使用するのではなく、新しいコンテナを起動するたびに kubelet が Docker Hub からイメージをPullするように、Always に設定されます。これには、以前に作成したDockerレジストリシークレットによる認証が必要です。

cat <<EOF > nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: dev
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      serviceAccountName: dev-sa
      containers:
      - name: nginx
        image: <USER_NAME>/<REPO_NAME>:<TAG_NAME>
        imagePullPolicy: Always
        ports:
        - containerPort: 80
EOF

次のコマンドを使用して、構成ファイルを適用し、EKSクラスターに Deployment を作成します。

kubectl apply -f nginx-deployment.yaml

ロードバランサーの作成

最後に、デプロイメントの pod を公開する外部 LoadBalancer タイプの service をプロビジョニングします。

kubectl expose deployment nginx-deployment \
    --namespace=dev \
    --type=LoadBalancer \
    --name=nginx-service

Service に関連付けられている Elastic Load Balancer の DNS エンドポイントを取得します。

kubectl get service/nginx-service --namespace=dev

ブラウザを使用して、EXTERNAL-IP 出力フィールドで指定された DNS エンドポイントに移動します。 デフォルトのNGINX ウェルカムページを表示できること、およびデプロイメント内の pod が認証用の資格情報を使用してプライベート Docker Hub リポジトリからコンテナイメージを正常にPullできたことを確認します。

クリーンアップ

Service と関連する Elastic Load Balancer を削除します。

kubectl delete service nginx-service --namespace=dev

eksctl delete cluster コマンドを使用して、EKSクラスターを削除します。

eksctl delete cluster eks-dev

まとめ

この投稿では、Amazon ECSAmazon EKS の両方を使用して2つのクラスターを作成し、プライベート Docker Hubリポジトリからコンテナイメージを Pull するように構成しました。 AWS Key Management Service との統合により、Docker Hub 認証情報の エンベロープ暗号化 を簡単に実装できます。 Docker Hub で認証することにより、ProまたはTeamプランを使用するときに、コンテナイメージ Pull に新しく導入された Rate limits を回避できます。プライベートリポジトリは、機密性の高いコンテナイメージのアクセス制御標準を維持するのに役立ちます。

翻訳はソリューションアーキテクトの竹本が担当しました。