✏️ 👤

Kubernetes クラスタが AWS 上でコンテナイメージを pull するときの権限まわりの話

あるいは『AWS 上で動く Kubernetes クラスタが Amazon ECR からコンテナイメージを pull するときの IAM とか権限とかってどういう仕組みで動いてるの?』という記事です.

コンテナ1を実行するためには、原則としてまずはコンテナイメージをダウンロードしてくる必要があります. 例えば手元のマシンなどでコンテナを実行する際は、一般的には docker pull のようなコマンドを利用してコンテナイメージをダウンロードしてきますね. パブリックなコンテナイメージ2であれば、特に認証などせずともダウンロードが可能なのはみなさま良くご存知の通りです.

しかし、実際にプロダクションで利用されるコンテナイメージの多くは、その会社や組織固有のアプリケーションコードなどを含んでいることがほとんどであり、これらはパブリックではなくプライベートな、つまりそのダウンロードに認証や認可を必要とするコンテナイメージとして管理されることが多いでしょう.

Amazon ECR はそのようなプライベートな認証・認可を要するコンテナイメージを格納し、アクセス権限がある人間やシステムに対してそれらを利用可能にするためのマネージド・サービスの1つです. 「アクセス権限」は他の AWS サービス同様、IAM と呼ばれる AWS 上の認証・認可の機構を利用して明示的に設定します.

ということで、AWS 上で実行される Amazon EKS クラスタや Self-hosted な Kubernetes クラスタは IAM を使って Amazon ECR の認証・認可を通し、コンテナイメージをダウンロードしているはずですね.

Kubernetes クラスタがプライベートなコンテナイメージをダウンロードする一連の流れ

ここでは Kubernetes v1.19.7 における実装を例に挙げます. なんでバージョンを明示して話をするのかについては後述します.

Kubernetes クラスタにおいてコンテナイメージを pull する処理の主役はクラスタを構成する各ノードで実行されている kubelet です.

kubelet は、Pod 作成にあたってその Pod が利用するコンテナイメージをダウンロードする必要があるかを諸々の前提条件3を勘案しつつ判断し、ダウンロードの必要がある場合には dockerd (あるいは CRI ランタイム)に対してコンテナイメージの pull を指示します.

ここでダウンロードしてくるコンテナイメージがパブリックなものであればそのまま pull してくれば良いのですが、先述したように Amazon ECR からコンテナイメージをダウンロードしてくる場合には IAM での認証・認可をパスしてなければなりません.

Amazon ECR からコンテナイメージを pull する際のトークンは Amazon ECR の GetAuthorizationToken API を呼ぶことで取得できます. AWS CLI から叩くと以下のような感じです.

$ aws ecr get-authorization-token --registry-ids <SPACE-DELIMITED-AWS_ACCOUNT_ID-LIST>
{
    "authorizationData": [
        {
            "authorizationToken": "QVdTOkN...",
            "expiresAt": ...
        }
    ]
}

余談ですが、叩いたトークンを直接 Docker に読み込ませたい場合には get-login-password というコマンドもあります. ローカルで Docker を利用している方はこちらのコマンドの方が見慣れているかもしれませんね.
余談のさらに余談ですが、GetLoginPassword という API は実は ECR には存在しません. get-login-passoword はあくまでも CLI 側でのみ実装されているコマンドで、内部的には先述の GetAuthorizationToken API を叩いてトークンの値だけを取り出しています.

$ aws ecr get-login-password \
    --region <region> \
    | docker login \
        --username AWS \
        --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com
Login Succeeded

さて、今回のテーマである Kubernetes クラスタにおいては、コンテナランタイムが dockerd であるかどうかは自明ではありませんので、kubelet が前者の API GetAuthorizationToken を利用してトークンを取得し、コンテナランタイムに pull を指示する際に一緒に渡してあげています. コードでいうと例えば pkg/kubelet/images/image_manager.go#L144 あたりが挙げられます.

それじゃあ Amazon ECR からのトークンはどこで取得しているのか(≒ どこで GetAutorizationToken API を呼んでいるのか)というと、pkg/credentialprovider/aws/aws_credentials.go#L134 がそれに当たります.

ここまでをザックリまとめると、1) kubelet が Amazon ECR の GetAuthorizationToken API を呼びだしてトークンを取得し、2) dockerd (あるいは CRI ランタイム)に対してトークンを渡しつつ pull を指示している、という流れになります.

しかし、AWS を使ったことがある方であれば想像がつく通り、kubelet が Amazon ECR の API を呼び出すためには kubelet 自身がその呼び出しを許可された IAM ロールを持っていなければなりません. これはどこで渡されてるんでしょうか.

Self-hosted な Kubernets クラスタや EKS/EC2 のような、kubelet が EC2 上で動作する環境においては、その EC2 インスタンスにインスタンスプロファイルを設定することで、kubelet は EC2 インスタンスメタデータサービス(IMDS) を通してその IAM ロールを取得します. EKS/Fargate の場合には Fargate プロファイルに紐づける Pod execution role で kubelet に渡したい IAM ロールを指定することができます. 具体的な実装については kubelet が vendoring して利用している AWS SDK Go に書いてあるため、kubelet そのもののには特別な実装は組み込まれていません.

とても straightforward な作りに見えます.

NOTE: 今回の記事では言及しませんが、実は上記以外にも Kubernetes クラスタにおいて Amazon ECR の認証・認可を通しつつコンテナイメージを pull する方法はいくつかあります. これらについては別記事でまた書こうと思います.

k/k in-tree 実装

Kubernetes フリーク勢はもはやお気づきのとおり、上に挙げた実装はすべて kubernetes/kubernetes リポジトリ内に in-tree で実装されています.

現時点では AWS 以外にも Azure と GCP の実装が入っていますが、例えば既存実装に存在しない自分たち固有のコンテナイメージレジストリをこの仕組みに載っけたい場合には、Kubernetes 本体のリポジトリにその実装がマージされなければなりません. 加えてそれが Kubernetes リリースバージョンに載ってくるまでにはさらに時間がかかります.

本体にマージするのではなくフォークした自分たちの Kubernetes コードを管理していく方法もあるかもしれませんが、これは OSS 界におけるアンチパターンの1つでしょう.

つまり、例えば CNI や CSI が解決しようとした課題の1つ4と同じような問題点がこの kubelet の実装にはあるわけです.

kubelet - Credential providers

というわけで、上に挙げたような実装を外出しにすることを目指した改善が kubelet の “Credential providers” です.

昨年の暮れに kubernetes/kubernetes #94196 においてその alpha 実装がマージされており、元となる KEP は KEP-2133: Kubelet Credential Providers です. Kubernetes v1.20.0 リリースに含まれています.

Credential providers の実装については本記事では掘り下げませんが、最後に KEP から Credential providers のモチベーションとして書かれている部分を引用して締めようと思います.

> This is part of a larger effort in the project to remove built-in functionality that favors any specific cloud provider. The ACR, ECR and GCR implementations should be removed in favor of an extensible plugin mechanism that can be used by any cloud provider without adding built-in logic into the kubelet.

いいですねぇ.


  1. 本記事における「コンテナ」はいわゆる OCI コンテナを指します. 本記事においては Docker コンテナと同義と思って読み進めていただいても支障ありません ↩︎

  2. 例えば amazonlinux コンテナイメージが Docker HubAmazon ECR Public でパブリックに公開・配布されています ↩︎

  3. 例えば Pod spec に記述する imagePullPolicyAlwaysIfNotPresent はメジャーな条件の1つかと思います ↩︎

  4. ブログ記事 “Container Storage Interface (CSI) for Kubernetes GA” の “Why CSI?” セクションなんかが分かりやすいと思います ↩︎