EKSのIAM Role for Service Accountsを使用するときにはSTSのリージョナルエンドポイントを使うように設定しようという話
先日、DynamoDBにアクセスするAPIをLambdaからEKS上への再実装・載せ替えを行いました。その際、IAM Role for Service Account(IRSA)を使用した一時クレデンシャル取得時のレイテンシが大きく高まる事象に遭遇しました。
原因と解決策を調査から解決に至るまでの過程とともにご紹介します。
TL;DR
IRSAを使用する際は一時クレデンシャル取得時にSTSにリージョナルエンドポイントを使用させるためAWS_STS_REGIONAL_ENDPOINTS=regional
という環境変数を設定することを検討すること。
経緯
現在転職会議ではECS or Lambdaで実装された既存のサービスを順次EKS上に載せ替えていっています。
先日、私がLambdaで実装されていたAPIをGoで再実装しEKS上に載せ替えました。このAPIはDynamoDBの中身をレスポンスとして返すという極めてシンプルなAPIです。
リプレース後、レイテンシの平均をとってみると以前のLambdaでの実装よりも速くはなっていたのですが、1時間おきにレスポンスが2s程度まで跳ねあがる現象が発生しました。
Datadog APMでレイテンシ急増時のフレームグラフを見てみると、2秒かかっているうちDynamoDBへのアクセスにかかっているのはリクエスト処理時間のうち最後の方の0.2秒程度だけであることが分かりました。
ここまでのことから、DynamoDBにアクセスする前に必要な処理でありかつ1時間おき発生する処理が原因と考えられ、その両方に当てはまるIAM Role for Service Accounts(IRSA)による一時クレデンシャルの取得処理が疑わしいと考えました。
そこで、行われている動作をもう少し詳しく調べるため、ステージング環境でtcpdumpで調べてみることにしました。具体的な手順としては以下です。
kubectl exec
でAPIサーバのコンテナに入る- tcpdumpをコンテナにインストール
- tcpdumpでダンプを取得
- ダンプファイルをkubectl cpでローカルにコピー
- Wiresharkでダンプファイルを見る
ダンプファイルを見てみると、STSとの通信ログからSTSへのアクセスに1.5s程度時間がかかっていることがわかりました。どうやら、Kubernetesの発行したトークンで一時クレデンシャルを取得するSTSのAssumeRoleWebIdentity
で時間がかかっているようです。
ダンプの中身を詳しく見てみると、DNSの通信ログからそのSTSへのアクセスにグローバルエンドポイント(sts.amazonaws.com
)が使用されていることがわかります。
STSでは可能な場合はリージョナルエンドポイント(sts.<region名>.amazonaws.com
)を使用することが推奨されています。そこで、AWS_STS_REGIONAL_ENDPOINTS=regional
という環境変数を設定してリージョナルエンドポイントが使用されるように設定しました。
すると、デプロイ直後と1時間毎に発生している一時クレデンシャル取得時のAPIのレイテンシが2秒から0.4秒程度まで大きく改善しました!
念の為ダンプファイルをとってみると、変更後は一時クレデンシャル取得時にはちゃんとSTSのリージョナルエンドポイントと通信していました。
まとめ
IRSAにより一時クレデンシャルを取得してAWSのAPIへアクセスするときはAWS_STS_REGIONAL_ENDPOINTS=regional
という環境変数を設定することを検討してください。この環境変数は最近のバージョンのAWS SDKおよびAWS CLIであれば使用できるようです。(ほかにSTSのリージョナルエンドポイントを使用させるもっとよい方法があればこっそり教えて下さい)
問題調査をするときには必要とあれば抽象化の皮を剥がして調査する能力が大事だと強く感じる事件でした。
参考資料
株式会社リブセンスに入社して1ヶ月が経ちました
はやいもので明日でリブセンス入社1ヶ月目の業務が終わり今年の仕事納めとなります。
今回の記事では私の入社1ヶ月での業務内容や入社1ヶ月目の所感などを書いていきます。
やったこと
キャッチアップ
入社時点で配属先である転職会議のインフラ周りがかなり整っていたことや、1on1等を厚めに行ってくれたこともあり、現状の把握に大きく時間を取られることがありませんでした。
AWS Lambda(Node.js)で作られたAPIをGo(Gin) on EKSにリプレース
- コピペされることを想定してKubernetesのYAMLは基本的なベストプラクティスを意識して作成
- limit, requestsの設定
- preStopでsleep
- PodDisruptionBudget
- Webサーバも最低限必要そうな事項を意識して実装
- IRSAを使用したことに関連するパフォーマンスバグ対応(後日記事化)
DatadogのエージェントをEKSクラスタに導入
- アラートの設定
- ダッシュボードの設定
RDSの証明書更新対応準備
- メンテ日時の設定
- ステージング環境でのリハーサル
HackDays(年末最終週がリリース禁止であるため、その間エンジニアが好きな技術で遊んで成果を発表する社内イベント)
その他
入社1ヶ月の所感
事業会社のSREとして働くのは初めての経験でしたが、周りの方のサポートもありスムーズに入っていくことができました。一緒に働くエンジニアの方々はみんなそれぞれレベルが高く、担当領域を超えたところにも改善を行っていく強いマインドを持っていて一緒に働いていてとても楽しい1ヶ月でした。
コミュニケーションの面でも1 on 1やエンジニアが全員集まって課題を話し合うMTGなどうまく交通整理されており、かなりやりやすさを感じました。
転職会議のインフラ面での現在の課題である全面EKS化に向け、来年はさらに気合いを入れて頑張っていきたい所存です。
EKS上でもBerglasみたいにクレデンシャルをいい感じにしたい!!!
こちらはAmazon EKS #1 Advent Calendar 2019 4日目の記事です。
こんにちは、かたいなかです。株式会社リブセンスに入社してはや一ヶ月弱が経過しました。
私が所属する転職会議事業部では、ECSからEKSへの移行を進めています。その中で現在議論が白熱しているのがクレデンシャルの管理方法についてです。検討の中で「GCPにはBerglasという便利ツールがあり、Podに渡す環境変数をよしなに指定するとSecret Managerにあるクレデンシャルをいい感じに展開してくれる」という話を同僚に聞きました。
そこで、今回はMutationWebhookの実装の素振りを兼ねてEKS上でAWS Secrets ManagerからBerglas同様にクレデンシャルを取得するしくみを作ってみましたのでご紹介します。
TL;DR
Beglas風にAWS Secrets Managerからクレデンシャルを取ってきてくれるツール(Not Production Ready)を試験的に作成。MutationWebhookは便利だが劇薬なので用法用量注意が必要。
Berglasとは
BerglasはGCPが提供しているGCP上に保存されたクレデンシャルを便利に扱うツールです。
BerglasのCLIにはberglas exec --
というコマンドで実行するラッパーの機能があります。このコマンドを実行する際、berglas://<credentialへの参照>
という形式の値を持つ環境変数があると、当該の環境変数を参照先から取得した実際の値に置き換えてラップされたコマンドを実行します。
$ export API_KEY="berglas://<credentialへの参照>" #"API_KEY"の値が参照先のクレデンシャルの値で置き換わった状態で"--"の後ろに指定したコマンドを実行 $ berglas exec -- some command
MutationWebhook
前節で紹介したCLIツールをどのようにKubernetesと連携させればよいかについては、BerglasのGitHubリポジトリにKubernetesのMutationWebhookのサンプル実装が置かれています。(https://github.com/GoogleCloudPlatform/berglas/tree/master/examples/kubernetes)
このサンプル実装では、Podの作成時にberglas://<credentialへの参照>
の環境変数が設定されている場合、initContainer
でBergalsのコンテナからemptyDir
のボリュームにバイナリをコピーする設定を追加します((1)の部分)。そして、commandを書き換え、もともと指定されていたコマンドをberglas exec --
でラップするように設定します((2)の部分)。以下のようなYAMLが作成されます。
apiVersion: v1 kind: Pod metadata: name: envserver spec: # ボリュームにberglasのbinを流し込むinitContainersを追加(1) initContainers: - name: copy-berglas-bin image: gcr.io/berglas/berglas:latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "cp /bin/berglas /berglas/bin/"] # berglasのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-bin mountPath: /berglas/bin/ containers: - name: server image: katainaka0503/server imagePullPolicy: Always # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2) # command: ["/bin/server"] command: ["/berglas/bin/berglas"] args: ["exec", "--", "/bin/server"] env: # berglas://の形式の環境変数がある場合にPodを編集 - name: API_KEY value: berglas://berglas-test-secrets/api-key - name: TLS_KEY value: berglas://berglas-test-secrets/tls-key?destination=tempfile # berglasのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-bin mountPath: /berglas/bin/ volumes: # berglasのバイナリ流し込み用ボリューム - name: berglas-bin emptyDir: medium: Memory
これにより以下のような利便性が提供されます。
- 環境変数でGCP上に保存されたSecretの参照を指定するかたちのため、リポジトリ上でのYAMLの暗号化等は不要
- 実行時に
emptyDir
ボリューム経由でラッパーのバイナリを埋め込むことで、アプリケーションのDockerfileに手を入れることなくKubernetesデプロイ時にラッパーを実行 - Podのenv等にクレデンシャルが展開されたりもしないので機密性が高い
ただし、Podの設定のcommand
, args
で指定しているコマンドをラッパースクリプトの中で実行させる仕組みであるため、command
, args
がPodのYAMLの中で明示的に指定されている必要があります。(Dockerfileで指定したENTRYPOINT, CMDをそのまま使わせることができない)
AWSでもBerglas風にクレデンシャルを管理したい
さて、ここからが本題です。GCPでできることはAWS上でも同じようにやりたくなるのが自然な心理です。そこで、今回はBerglasと同様の仕組みでSecretsManagerから値を取得できるようなWebhookおよびCLIを写経を兼ねて実装してみました。
今回のコードは私が自身の学習用に作成したものです。継続的にメンテナンスするつもりは現在のところないので、そのまま使用することはおすすめしません。
動作
今回作成したものをデプロイした状態で以下のようなYAMLでPodを作成しようとすると、
apiVersion: v1 kind: Pod metadata: name: server annotations: eks.amazonaws.com/role-arn: hogehoge spec: containers: - name: server image: server:any-tag command: ["/bin/server"] # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集 env: - name: API_KEY value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID>
以下のようなYAMLに修正されてPodが作成されます。
apiVersion: v1 kind: Pod metadata: name: server annotations: eks.amazonaws.com/role-arn: hogehoge spec: # ボリュームにberglas-awsのbinを流し込むinitContainersのを追加 initContainers: - name: copy-berglas-aws-bin image: katainaka0503/berglas-aws:latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "cp $(which berglas-aws) /berglas-aws/bin/"] # berglas-awsのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-aws-bin mountPath: /berglas-aws/bin/ containers: - name: server image: katainaka0503/server imagePullPolicy: Always # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2) # command: ["/bin/server"] command: ["/berglas-aws/bin/berglas-aws"] args: ["exec", "--", "/bin/server"] env: # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集 - name: API_KEY value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID> # berglas-awsのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-aws-bin mountPath: /berglas-aws/bin/ # berglas-awsのバイナリ流し込み用ボリューム volumes: - name: berglas-aws-bin emptyDir: medium: Memory
これにより、特定の形式で環境変数を指定するとAWS Secrets Managerに保存した値を取ってきて環境変数に設定した状態でコマンドを実行してくれるようになります。
CLI
CLI部分はBerglasの実装を参考に実装しました。
https://github.com/katainaka0503/berglas-aws
本家Berglasと同様に、ラップされたコマンドを実行する際に、berglas-aws://<SecretsManagerのARN>
という形式の環境変数があればSecretManagerから取得した実際のシークレットの値で当該環境変数を置き換えます。
$ export API_KEY="berglas-aws://<SecretsManagerのARN>" #"APK_KEY"の値が参照先のAWS Secrets Managerに保存された値で置き換わった状態で"--"の後ろに指定したコマンドを実行 $ berglas-aws exec -- some command
Webhookの実装
Webhookの部分も同様にBerglasのKubernetesへのデプロイのサンプルを参考にkube-webhook
というフレームワークを使用して作成しました。
https://github.com/katainaka0503/berglas-aws-webhook
こちらも本家と同様の仕組みで、Pod作成時にberglas-aws://<SecretsManagerのARN>
という形式の環境変数がある場合にcommand
とargs
をいじってberglas-aws exec --
でラップして実行されるように修正します。
Kubernetesへのデプロイ
最後にWebhookをKubernetesにデプロイする部分です。
https://github.com/katainaka0503/berglas-aws-webhook/tree/master/deploy
MutationWebhookを設定する際には、Webhookサーバ用の証明書とkube-apiserverがWebhookサーバが提供する証明書を検証するためのルート証明書が必要です。今回はCertManagerを使用して証明書を発行しました。それ以外は概ね通常のDeploymentとServiceを使用したAPIサーバの構成です。
まとめ
Secret管理方法の検討とMutationWebhookの学習を兼ねてBerglas風にAWS Secrets Managerからクレデンシャルを取得できるツールを作成して遊んでみました。想像の数倍容易に実装できたので意外でした。実装したツールを継続的にメンテナンスしていける組織であれば、このようなWebhookの仕組みを用意する方法がシークレットの管理方法として選択肢に入ってくるのかもしれません。
今回のコード
参考資料
補足: ECSでは
ちなみにECSでは、ここまで紹介してきたようなECSのタスク起動時にクレデンシャルの値をとってきて環境変数に設定する機能がすでに提供されています。タスク定義内のコンテナ定義のsecretsという属性にAWS Secrets Manager/AWS Systems ManagerパラメータストアのARNを指定すれば、参照先から保存された値を取ってきてくれます。
EKSでの認証認可 〜aws-iam-authenticatorとIRSAのしくみ〜
こちらはAmazon EKS #1 Advent Calendar 2019 7日目の記事です。
EKSでIAM RoleをUserAccountに紐付けたり、ServiceAccountをIAM Roleに紐付けたりする際、AWSのドキュメントに従って設定してはいるものの、その設定によって実際にどんな処理が行われているかを具体的に知らない方も多いのではないでしょうか?(私も今回の記事のために調べるまではそうでした。)
そこで今回の記事では、Kubernetesの認証認可の仕組みを解説したあと、AWSのIAMの認証情報をKubernetes内のUserAccountに紐付けるaws-iam-authenticatorの動作の仕組みとKubernetesのService AccountにIAM Roleを紐づける仕組みについて設定方法のレベルから一段掘り下げて実際の動作に焦点を当てながら説明していきます。
目次
- 目次
- Kubernetesの認証認可・AdmissionControl
- EKSのUserAccount(aws-iam-authenticator)
- ServiceAccountのIAM ロール(IRSA)
- まとめ
- 参考資料
Kubernetesの認証認可・AdmissionControl
まずは事前知識として、Kubernetesの認証認可およびAdmission Controlの仕組みについておさらいしていきましょう。KubernetesでのAPIサーバへのリクエストは、実際にその内容に従って処理を行う前に以下の各stageで順番に処理されます。
- Authentication(認証)
- Authorization(認可)
- Admission Control (リクエストのバリデーション・変更等)
それぞれ順番に見ていきましょう。
Authentication(認証)
認証方法
KubernetesではBasic認証を使用した方法やクライアント証明書を使用する方法など、様々な認証方法が提供されています。
EKSでは主に以下の2つの認証方法が使用されています。
- Service Account Tokens
- Webhook Token Authentication
認証主体
Kubernetesの認証主体は以下の2種類です。
- ServiceAccount
- UserAccount
ServiceAccount
ServiceAccountはKubernetesのAPIによって管理される名前空間に紐付いたリソースです。
ServiceAccountごとに、Kubernetesクラスタが発行したトークンをクラスタ内のSecretとして保持しています。Podのコンテナにこのトークンを埋め込み、Pod内のプロセスにこのトークンを使用させることでkube-apiserver
から認証されます。
ServiceAccountは以下のようなYAMLで定義します。
apiVersion: v1 kind: ServiceAccount metadata: name: test namespace: default secrets: # ServiceAccount作成後のSecret作成に伴って追加される - name: test-token-mfb7n
ServiceAccountを作成すると、TokenControllerによってトークンを保持する以下のようなSecretが作成されます。
# 見やすいよう項目の順番を入れ替えた apiVersion: v1 kind: Secret type: kubernetes.io/service-account-token metadata: # 省略 name: test-token-xxxxx namespace: default # 省略 data: ca.crt: LS0...(省略)...= # APIサーバのCA証明書がBase64エンコードされたもの namespace: ZGVmYXVsdA== # 名前空間名がBase64エンコードされたもの token: ZXI...(省略)...= # JWT形式のトークンがBase64エンコードされたもの
このようなSecretはServiceAccountを指定してPodを作成すると、自動でマウントされます。具体的には、後述するAdmission Controllerの仕組みによってPodに以下のような設定が追加されます。
volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: test-token-xxxx readOnly: true
volumes: - name: test-token-xxxx secret: defaultMode: 420 secretName: test-token-xxxxx
これにより、コンテナ内の/var/run/secrets/kubernetes.io/
に以下のような3つのファイルが作成されます。
なお、ServiceAccountを指定せずにPodを作成すると、Podと同じnamepaceのdefault
という名前のServiceAccountの指定が追加され、default
のServiceAccountに紐付いたトークンが埋め込まれます。これも後述するAdmission Controlによるものです。
KubernetesのGo ClientではPodにトークンが埋め込まれた状態で以下のような設定をすることで、APIサーバへのアクセス時にAuthorization: Bearer
ヘッダでトークンを送信するようになり、認証された状態で操作を行うことができるようになります。(エラーハンドリング処理は省略)
config, err := rest.InClusterConfig() clientset, err := kubernetes.NewForConfig(config)
UserAccount
UserAccountはServiceAccountとは違い、Kubernetesの外部で管理されているユーザです(=kubectlでUserAccountを作成するようなことはしない)。外部の認証情報と連携して使用されることが意図されており、例えばGKEではGCPのアカウントとリンクしていたりします。
EKSの場合はaws-iam-authenticatorという仕組みを使用し、IAMの認証情報から得たトークンを使用しKubernetes内のユーザに紐付けることができます。こちらは後ほど詳しく解説します。
Authorization(認可)
Kubernetesでの認可はRBAC(Role Based Access Control)と呼ばれる仕組みです。RBACではRole or ClusterRoleで許可する操作を定義し、RoleBinding or ClusterRoleBindingでServiceAccount等に紐付けます。Role or ClusterRoleはAWS IAMロールと名前は似ていますが認証される主体ではなく、むしろ権限を定義するIAMポリシーに近いものです。
以下はClusterRoleとClusterRoleBindingでServiceAccountにPodに対する操作を許可する場合の例です。
まず、ClusterRoleで許可する操作を定義します。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: namespace: default name: pod-reader rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list"]
これをClusterRoleBindingでServiceAccountに紐付けます。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: read-pods-test subjects: - kind: ServiceAccount name: test apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: pod-reader apiGroup: rbac.authorization.k8s.io
これにより、ServiceAccountが紐付いたPod内からClusterRoleで許可された操作が行えるようになります。
Admission Control
Kubernetesでは、認証及び認可の処理が終わったリクエストを実行する前に、さらにAdmission Controlという仕組みで内容のバリデーションと修正を行います。
様々なAdmissionControllerが用意されており、基本的にはkube-apiserverと一緒にコンパイルされ同じバイナリにまとめられています。ただし、MutatingAdmissionWebhook
や ValidatingAdmissionWebhook
を使用することで Webhookで外部のプロセスにバリデーションおよび修正の処理を委譲できます。このようなWebhoookの設定はKubernetesのリソースとして管理されており、EKSではデフォルトで以下のような設定が生えています
$ kubectl get mutatingwebhookconfiguration NAME CREATED AT pod-identity-webhook 2019-11-22T12:59:57Z
先程のServiceAccountの解説の中でPod作成時に自動的にトークンがマウントされたり、ServiceAccountが指定されていないときにはdefaultのServiceAccountの指定が追加されていましたが、これはTokenController
によりPodの作成のリクエストが修正されたためです。
また、後で説明するServiceAccountにIAMロールを紐づける仕組みの中でもトークンや環境変数の設定を MutatingAdmissionWebhook
でPodに追加しています。
EKSのUserAccount(aws-iam-authenticator)
EKSではaws-iam-authenticatorによりIAMのエンティティとKubernetesのUserAccount/Groupを紐付けます。
ここでは、紐付けに必要な設定を見たあとで、実際にどのような処理が行われているかを掘り下げてみていきます。
設定内容
aws-iam-authenticatorでIAMのエンティティとKubernetesのUserAccount/Groupを紐づけを定義するのは以下のような aws-auth
ConfigMapです。
apiVersion: v1 data: mapRoles: | - rolearn: arn:aws:iam::XXXXXXXXXXXX:role/<ワーカーノードのロール名> username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes mapUsers: | - userarn: arn:aws:iam::XXXXXXXXXXXX:user/admin username: admin groups: - system:masters kind: ConfigMap metadata: # 省略 name: aws-auth namespace: kube-system # 省略
EKSのkube-apiserverにIAMの認証情報からトークンを作成してアクセスするにはaws eks update-kubeconfig
コマンド等で以下のようなkubeconfigファイルを作成します。
# 省略 users: - name: arn:aws:eks:ap-northeast-1:<アカウントID>:cluster/<クラスタ名> user: exec: apiVersion: client.authentication.k8s.io/v1alpha1 command: aws args: - --region - ap-northeast-1 - eks - get-token - --cluster-name - <クラスタ名>
これらの設定により、クライアント側のIAMエンティティとaws-auth
ConfigMapで定義した紐付けに従ってKubernetesのUserAccount/Groupとして認証されます。
余談ですが、将来のaws-iam-authenticatorのバージョンでは、このようなCustomResourceにより管理するようになるようです。これにより、1つの大きなConfigMapを使ってすべての紐付けを管理せず、IAMエンティティごとに別々に紐付けを定義するようになるようです。(https://github.com/kubernetes-sigs/aws-iam-authenticator/pull/116)
実際の処理
では、実際には前節の設定でどんな処理が行われているのでしょうか?
aws-iam-authenticator
による認証の流れは以下のようになります。
まず、kubeconfigの設定により、kube-apiserverへのアクセス前に、トークンを取得するためaws eks get-token
コマンドが実行されます。これにより、STSのGetCallerIdentity
の署名付きURLが発行され、これをもとにトークンが作成されます。
トークンの検証時にはこのトークンから抜き出した署名付きURLでGetCallerIdentity
を実行します。GetCallerIdentity
では署名付きURLを発行したIAMエンティティの情報が得られます。これにより、クライアントが特定のIAMのエンティティであることが確かめられるというわけです。
クライアントがAPIサーバにリクエストする際には、Authorization: Bearer
ヘッダでトークンをいっしょに送信します。
ここから先はEKSのコントロールプレーン内での処理です。
トークンを受け取ったkube-apiserverはWebhook Token Authenticationの仕組みでaws-iam-authenticatorサーバにトークンの検証を委譲します。
aws-iam-authenticatorサーバではトークンから抜き出した署名付きURLでGetCallerIdentity
を実行します。結果として得られたIAMのエンティティの情報とaws-auth
ConfigMapの情報を突き合わせてIAMのエンティティに紐づくUserAccount/Groupを取得します。そして、kube-apiserverにUserAccount/Groupの情報を返します。
このような流れにより、リクエストが特定のグループの特定のユーザからのものとして認証されます。
ServiceAccountのIAM ロール(IRSA)
先程のaws-iam-authenticatorの仕組みは、IAMのエンティティをKubernetesのユーザアカウントに紐付ける仕組みでした。
逆に、Kubernetes内部にいるPodのService Accountの認証情報を使用してIAM Roleを引き受けるためにはいくつかの方法があります。
- Service Accountのトークンを使用してIAM Roleを引き受ける(IAM Role for Service Account 以下 IRSA)
- ノードのiptablesをいじくって、メタデータエンドポイントへのアクセスを横取りしていい感じにAssumeRoleできるようにする(KIAM/kube2iam)
- AWSの認証情報をSecretにして埋め込む
ここでは、IRSAによりService AccountにAWSのロールを紐付ける方法をみていきます。
実際にはeksctlの便利なコマンドが用意されているため、実際にどのような設定が行われるのか深く意識せずとも使えますが、ここでもあえて一段掘り下げて何をやっているのかを順番に見ていきます。
概要
実際の動作のフローとしては以下のようになります。
IAMロールがアノテートされたServiceAccountを準備
ServiceAccountのトークンでのAssumeRoleを許可したIAMロールを準備
このServiceAccountを指定したPod作成時、AdmissionControllerによりSTS向けのServiceAccountのトークンの埋め込みと必要な環境変数の設定が行われる
以下に示すバージョンより新しいAWS SDK動作時にはコンテナに埋め込まれているトークンを元にAssumeRoleWithWebIdenityを実行し、IAM Roleを引き受ける
図にすると以下です(Amazon Web Services ブログの記事より転載させていただきました)
設定
では実際の設定を見ていきながらそれぞれ確認していきましょう。
AWSでOIDCプロバイダーを作成
まず、IAMのOIDC providerというエンティティを作成します。これを作成することで、EKSクラスタから発行されたトークンを信頼するようになり、AssumeRoleWithWebIdentityによるWebフェデレーションが行えるようになります。
eksctlでは以下のようなコマンドで実行しますが、
eksctl utils associate-iam-oidc-provider \ --name floral-mongoose-1574427060 \ --approve
これは以下のようなコマンドと同等です。
EKSクラスタから発行されるIDトークンのISSUERのURLを取得し、IAMにOIDCのプロバイダとして設定することで、EKSクラスタが発行したIDトークンを信頼させています。
ISSUER_URL=$(aws eks describe-cluster \ --name irptest \ --query cluster.identity.oidc.issuer \ --output text) aws iam create-open-id-connect-provider \ --url $ISSUER_URL \ --thumbprint-list $ROOT_CA_FINGERPRINT \ --client-id-list sts.amazonaws.com
ServiceAccountとIAMRoleの作成
次にServiceAccountおよびそこからAsssumeRoleできるIAM Roleを作成していきます。eksctlを使用すると以下のようなコマンドで実行できます。
eksctl create iamserviceaccount --cluster floral-mongoose-1574427060 --name serviceaccount-iamrole-test --attach-policy-arn arn:aws:iam::aws:policy/PowerUserAccess --approve
このコマンドは、実際には以下のような信頼ポリシーを持つIAMロールを作成し、
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "$PROVIDER_ARN" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${ISSUER_HOSTPATH}:sub": "system:serviceaccount:default:<service account>" } } } ] }
以下のようなアノテーションがついたServiceAccountを作成しています。
eks.amazonaws.com/role-arn=<IAMロールのArn>
信頼ポリシーを見ると、Service Accountから発行されたトークンを使用してAssumeRoleできるように設定されていることがわかります。
PodでServiceAccountを指定
最後にPodでこのServiceAccountを指定し、Pod内のプロセスがIAMロールを引き受けられるようにします。
AssumeRole先のIAM Roleに関するアノテーションがついたServiceAccountを指定してPodを作成すると、 MutatingAdmissionWebhookで処理が委譲されたamazon-eks-pod-identity-webhookにより、Podにいくつか設定が追加されます。
まず、KubernetesのService Account Token Volume projectionという仕組みでaud
がsts.amazonaws.com
となっているIDトークンが発行され、/var/run/secrets/eks.amazonaws.com/serviceaccount/token
に埋め込むように設定が追加されます。
volumeMounts: # 省略 - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount name: aws-iam-token readOnly: true
volumes: - name: aws-iam-token projected: defaultMode: 420 sources: - serviceAccountToken: audience: sts.amazonaws.com expirationSeconds: 86400 path: token
そして、トークンの場所及び引き受けるIAMロールを表す環境変数が追加されます。
containers: - env: - name: AWS_ROLE_ARN value: arn:aws:iam::795113267886:role/eksctl-floral-mongoose-1574427060-addon-iams-Role1-1BHQ40Q5V2FWF - name: AWS_WEB_IDENTITY_TOKEN_FILE value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
このように環境変数が設定されトークンが埋め込まれている場合、ある一定のバージョン以上のAWS SDKであれば勝手にトークンを使用してAssumeRoleWithWebIdentity
でIAMロールを引き受けてくれます。
このように、IRSAは、Kubernetesの外部audience向けにService AccountのIDトークンを発行する仕組み(Service Account Token Volume Projection)とAWSのウェブ ID フェデレーションの仕組みがうまく噛み合うことで実現されています。
まとめ
aws-iam-authenticatorおよびIRSAの仕組みを解説しました。
aws-iam-authenticatorは署名付きURLを用いてクライアントが実際にIAMで認証されていることを検証する巧妙な方法で実現されていました。またIRSAの仕組みはOIDCというオープンな仕様の上でKubernetesとAWSの実装がうまく噛み合って実現されていました。
個人的に気になりつつも調べきれていなかったところではあったのですが、アドベントカレンダーという機会で半強制的に自分にプレッシャーを掛けて勉強できたので良かったなと感じています。
これからもEKSやっていくぞ!
参考資料
全体
- Kubernetes完全ガイド
- Controlling Access to the Kubernetes API - Kubernetes
- Authenticating - Kubernetes
- Kubernetesのユーザー管理と認証・権限確認機構を理解しよう | さくらのナレッジ
aws-iam-authenticator
- クラスター認証の管理 - Amazon EKS
- GitHub - kubernetes-sigs/aws-iam-authenticator: A tool to use AWS IAM credentials to authenticate to a Kubernetes cluster
- Authenticating - Kubernetes
- 署名付き URL の使用 - Amazon CloudFront
IRSA
- Kubernetes サービスアカウントに対するきめ細やかな IAM ロール割り当ての紹介 | Amazon Web Services ブログ
- Configure Service Accounts for Pods - Kubernetes
- Dynamic Admission Control - Kubernetes
- GitHub - aws/amazon-eks-pod-identity-webhook: Amazon EKS Pod Identity Webhook
- サービスアカウントの IAM ロール - Amazon EKS
- OpenID Connect(OIDC)ID プロバイダーの作成 - AWS Identity and Access Management
CKA受験ログ
ねんがんのCKAに88%で合格(合格ライン74%)したので、事前に行った勉強内容等を紹介する。
TL;DR
- Kubernetes 完全ガイドを 読んだ
- Kubernetes The Hard Wayをやった
- kubectl run --dry-run -o yaml でYAMLの雛形をすばやく作る方法を練習した
- 演習問題で手を動かした
- その他
- 試験に使用するPCはMBPで何の問題もなかった
- 身分証明書としてパスポートとクレジットカードを準備した
- 試験会場として貸し会議室を4時間(3時間+前後30分)借りた
試験概要
3時間で24問の問題を解き、74%以上得点できれば合格。得点は問題ごとに異なり、受験時には現在表示している問題の配点が画面上に表示されている。
問題とターミナルが表示されるタブに加え、1つだけ以下の3つのサイトを参照するためタブを開くことが許されている。
そのため、基本的には kubernetes.io/docs/ を開いているタブを参照しながら問題が表示されているタブ内のターミナルで解答していくことになる。
準備が必要なもの
- 試験を受けるのに使用するPCやWebカメラ等
- MBPで問題なかった
- 試験会場の確保
- 私の場合は貸し会議室を試験時間3時間に前後30分余裕をもたせて4時間確保した
- 身分証明書2種類
- パスポート等の顔写真と署名の入ったもの
- クレジットカード等の署名の入ったもの(私が受けた際には提示を求められなかった)
学習内容
勉強開始時点の知識(試験本番 2週間前)
- EKSを使用したインフラの構築経験(技術検証程度)
- Kubernetes 完全ガイドを半年前に1周読んだ状態
- Amazon ECSを使用したインフラの構築・運用経験
試験概要・範囲の確認
何よりも先に公式の資料を読んで試験の概要や範囲を確認した。
- GitHub - cncf/curriculum: 📚Open Source Curriculum for CNCF Certification Courses
- https://training.linuxfoundation.org/go/cka-ckad-candidate-handbook
- http://training.linuxfoundation.org/go//Important-Tips-CKA-CKAD
Kubernetes 完全ガイド
https://www.amazon.co.jp/dp/B07HFS7TDT/
読みながらひたすら手を動かした。
- 各リソースがどのような役割を持っていてどのような設定値があるか理解した
kubectl cordon
やkubectl rollout
等、馴染みの低かったコマンドを手を動かしながら意識して頭に入れるようにした- Helm等の周辺のプロダクトの章は試験対策としてはやらなかった
Kubernetes The Hard Way
-
- 日本語訳 github.com
Kubernetes The Hard Wayというリポジトリで紹介されている手順で何度かクラスタを組んだ。Kubernetesを構成するコンポーネントを一つ一つ手動で立てていくHardな手順であるため、Kubernetesの各コンポーネントの理解につながった。また、後述する演習問題をやる際にもここで作成したリポジトリを使用した。
この手順の中ではkubelet
やkube-apiserver
等をsystemd
を使用して立てていくため、systemd
の復習も軽くしておいた。
kubectl
の練習
スムーズに回答できるようにkubectl
の機能を復習しておいた。
- kubectl run/create/expose のススメ - Qiita
- kubectl explain — #HeptioProTip - Heptio
- Using kubectl to jumpstart a YAML file — #HeptioProTip
練習問題で手を動かす
一通り試験に出そうな内容が頭に入った状態にしたところで、手を動かす練習に移行した。
具体的にはHard Wayで作成したクラスタ上で、以下の2つのリポジトリを使って演習を行った。
公式ドキュメントのTasksをななめ読み
範囲に入ってそうなところをサラッと流し読みした。一通りの内容がドキュメントを参照すれば実行できる状態にしておいた。
その他
- 試験会場として貸し会議室を借りた。試験時間が3時間のため前後30分余裕をもたせて4時間確保した。
- 身分証明書としてパスポートとクレジットカードを準備した
やらなかったこと
- Linux FoundationやUdemyのCKA対策コース
当日
- 試験官とのやり取りはすべて英語でのチャット
- カメラでの会場の様子の確認と身分証明書の確認が終わり次第試験開始
- 試験中、通信が切れて再接続したりトイレに行きたくなったりしたが、チャット上で英語で説明すれば問題なく対応してもらえた
- 後半明らかに面倒そうな問題が出てきたが、すぐに捨てる判断をして正答できそうな問題の見直しに時間を割いた
感想
用意された環境に対して3時間ひたすら手を動かす試験であり、問題を解いていて楽しかったが体力をかなり消耗した。時間勝負の試験でありいかにスムーズに設定できるかが重要となるため、事前に手を動かして練習しておく重要性を感じた。
追記: CKADも合格した
CKA受験から3日後に特に追加の勉強をすることなくCKADを受験したが、91%(合格ライン66%)で合格できた。
範囲はCKAからクラスタの管理の話題を除いたサブセットである印象だったが、CKAよりずっとスピードを求められ、受験した際はかなり時間不足を意識させられた。そのため、不安な方は以下のリポジトリ等で手を動かす練習をしてから受験することをおすすめする。
約2年間勤めたクラスメソッド株式会社を退職しました
仕事内容
2017年9月の入社以降しばらくは、ScalaやRuby等でアプリのバックエンドのシステムのサーバサイドエンジニアをやってましたが、紆余屈折あってDevOps支援エンジニアとしてコンテナやCI/CDの導入支援等に携わり、最終的にB2BのサービスのSREに流れ着きました。
DevOps支援室時代は案件的にAWSのCodeシリーズを利用したCI/CD環境の導入やECS/EKSの技術支援に関わっていました。業務からちょっと外れたところでも、イケてる会社さんの取り組みを聞かせて頂いたり、ハンズオンを開催したりと、在籍期間中で一番楽しかった時期でした!
以下はDevOps支援室時代にマネージャだった藤村新さん(@aratafuji)にDevOpsDays Tokyo 2019でいじられる私の図
直近では tjinjin氏(@tjinjin)、wreulicke氏(@wreulicke)、ポン骨氏(@ponde_m)からの誘いもあり、事業開発部に社内転職してB2Bサービスに関わるSREとして動いていました。
具体的な業務としては、AutoScalingの導入、既存のTerraformのコードの運用フローの整備、Codenized Toolsで作られたレガシーのTerraformへのマイグレーション、増強縮退をはじめとする日々の運用オペレーション、障害対応などです。
良かったこと
ブログを書くことだけでなく外部向けの勉強会の開催等、アウトプットを後押ししてくれる会社でした。学んだことをアウトプットしていく習慣を身についたのは大きいなと感じています。
また、技術が好きな人ばかりなので、技術的な話が日常的にできるのは良いと思いました。特にサーバサイド/インフラの技術に関してはそれぞれ一家言ある人が揃っているので、議論しててとても楽しかったです。
なんでやめたの
入社以降一通り業務をやってきた中で、外の世界で自分がどれだけやれるのかを試してみたい気持ちが高まっていました。そんな折、仲の良かったのエンジニアが転職先で楽しそうにしているのを見るイベントが発生し、今がタイミングだなと思い転職活動しました。詳しい話は飲みの席で聞いてください。
今後
12月から株式会社リブセンスさんでお世話になります。
業務内容は今と同じAWS上で構築されたシステムに関わるSREですが、Kubernetes等使用した比較的モダンな環境を触ることになるのでニート期間にキャッチアップしていく所存。
Fastly Yamagoya Meetup 2019に行ってきた
2019/10/23 (水)に開催されたFastly Yamagoya Meetup 2019に行ってきた。
Twitter等で共有されてた登壇スライド
Measuring the performance. Let’s own your analytics tool.
Leveraging Cloud Portability with Fastly
CDNフル活用でつくる、高速Webアプリ / Using CDN To Improve Web Performance
レガシーなサイトにFastlyを入れて改善した道のり
なぜCDNを移行をしようと思ったか/ DAC 's CDN Migration Case Study
感想
「CDNはCloudFrontでいいだろ」みたいな思考停止をしていた自分(Fastly歴1日)にはとても刺激が強いイベントだった。
午前中はFiddleを使用したVCLのワークショップ。CloudFrontの設定変更の遅さに慣れていた自分にとっては設定変更して30秒弱待つだけで動作確認できるのはかなり驚きが強かった。(Lambda@Edgeの設定変更遅い)
VCLでちょっとしたコードを書くだけで地域判定してコンテンツを出し分けたり、IPベースでブロックしてみたりと非常に便利な印象だった
午後はトークセッションだったが「Fastly導入してみた」でとどまるような発表はなくそれぞれの内容がとても濃かった。
CDNでのヒット率を上げるためのキャッシュ戦略の話や、キャッシュのパージを自動化 or 非エンジニアが行える仕組み等の実際にどうやって運用しているかの話も面白かった。
発表を聞く中でFastlyの機能がかなりかゆいところに手が届いているように感じた。紹介されてた中で自分的に良いなと思ったのは以下のような機能。(一部ワークショップで聞いた機能あり)
- 高速パージ
- サロゲートキーを使ってコンテンツ単位でパージ
- Terraformでの管理(terraform-provider-fastly)
- 変更時にDraft版の変更だけにとどめて、本番でのactivateは手動でできたりする(activate=false)
- VCLでの柔軟な処理が簡単に組める
- クエリストリングの正規化によるキャッシュ率向上・HTTPヘッダの整理
- IPからリージョン/国を判定してそれをもとにコンテンツ出し分け
- ログ出力の柔軟さ
- リアルタイムなメトリクス取得
- Datadogとの連携も可能
実際に触ってみるともっと良さが見えてきそう。
個人的な話としても次の職場ではFastlyが導入されていると聞いてるので、高まったモチベーションで勉強進めたい。