こんにちは。
今年も昨年に引き続きF1のホンダ勢とルノー勢の動向が気になりつつ、でもDAZNもフジテレビNEXTも契約していないインフラチームの菊池です。
ピクスタではインフラプラットフォームにAWSを利用していますが、これまで各サービスのサーバーリソース(EC2)は主にOpsWorksを使って構築してきました。
しかし、近年の波に乗ってというか、OpsWorksの限界を感じてとういか、ここ最近はアプリケーションのコンテナ化に励んでおります。
開発環境にdockerを導入し、現在はステージング/プロダクション環境におけるkubernetes(以下k8s)を使ったコンテナ実行環境の構築や検証などを行なっています。
今回はEC2で稼働しているアプリケーションを、やはりEC2で稼働するk8sクラスターに移行する際に遭遇するであろうIAMロール(インスタンスプロファイル)の問題について取り上げてみたいと思います。
k8sでのインスタンスプロファイル
ピクスタの各サービスを構成するアプリケーションは主にAWSを使って構築されているので、EC2以外にもS3等各種AWSリソースを使っています。
アプリケーションがこのようなリソースにアクセスする際にはアクセス権が必要になりますが、ピクスタでは基本的にインスタンスプロファイルを利用してアプリケーションにアクセス権を渡しています。アプリケーション毎に必要となるアクセス権を持ったIAMロールを作成し、インスタンスプロファイルを使ってインスタンスに関連付けます。
現在の構成ではアプリケーション毎に稼働するインスタンスが異なっており、それぞれのアプリケーションが必要とする権限を適切に付与できます。アプリケーションに各種リソースへのアクセス権を渡す際のAWS推奨のやり方でもあります。
しかし、アプリケーションをコンテナ化しpodやdeploymentの単位でEC2上のk8sクラスターに配置するとなると話はそう簡単ではありません。
k8sクラスターでは不特定多数のコンテナが同一インスタンス上で実行されることになりますが、残念ながら現状ではpodやdeploymentの単位でIAMロールを割り当てる標準的な手順がありません。
何もしなければクラスター上の各コンテナはインスタンス(厳密にはそのコンテナが実行されるworkerノードインスタンス)に割り当てられたインスタンスプロファイル=IAMロールを取得することとなり、これが問題となります。
簡単な例で説明します。
例えば、AというS3バケットにアクセスする必要のあるPod:Aがあるとします。
バケットAへのアクセス権をworkerノードに割り当てられたIAMロールに付与すれば、Pod:AはバケットAへのアクセスが可能になります。
更に、BというS3バケットへのアクセス権を必要とするPod:Bを同じクラスターに追加するとします。
今度はworkerノードのIAMロールにバケットBへのアクセス権を付与すればPod:BはバケットBにアクセスできるようになります。
ただし、この時Pod:AもまたバケットBへのアクセスが可能になり、Pod:BはバケットAへのアクセスが可能です。
このように、EC2上のk8sクラスターで単純にインスタンスプロファイルとIAMロールを使ってコンテナに権限を与えようとした場合、クラスター内にアプリケーションが追加されるごとに各コンテナはworkerノードに関連付けられたIAMロールを共有し、場合によってはかなり強力な権限を持つことになります。
セキュリティ上好ましくない状況です。アプリケーションの誤動作や事故にも繋がりかねません。
kube2iamについて
概要
kube2iamもまたk8sクラスター内でコンテナ(daemonset)として実行されるものです。
dockerイメージが公開されており、自分で必要となるマニフェスト(daemonset等のyaml)を定義して導入することも可能ですが、helm chartとしても配布されており、helmが使える環境であればhelmからインストール可能です。
仕組み
kube2iamは、各コンテナからのインスタンスメタデータ取得リクエストを仲介します。
具体的には、kube2iamは各ノードに展開されるとそのノード上にiptablesの設定を追加します。このiptablesの設定により、各コンテナからの
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
のような認証情報取得リクエストを全てkube2iamのコンテナが引き受け、適宜処理してリクエスト元に返却するようになります。
kube2iamを導入済みのクラスターでは、以下のようにannotationに付与したいIAMロールを指定するだけで、podやdeploymentごとに権限を付与することが可能になります。
apiVersion: v1 kind: Pod metadata: name: mypod annotations: iam.amazonaws.com/role: role-arn
※別途IAMロールの信頼関係の設定も必要です
運用時に遭遇した問題
ピクスタでもkube2iamを導入してみましたが、いくつかの問題に遭遇しました。
導入は比較的簡単なのですが、前述のiptablesを使った仕組みといい、これは事前に知った上で導入すべきと感じたのでここで共有してみたいと思います。
削除してもIPtablesは残ったまま
一度導入すると、kube2iamを削除してもiptablesの設定は残ったままです。
後々変な挙動を起こさないよう導入前の状態に戻すには、面倒ですが手動でiptablesの削除が必要です。
ちなみに削除コマンドは以下のような感じです。(本当に久しぶりにiptablesのコマンドを打ちました)
// iptablesの設定を確認 # iptables-save | grep 169.254 // 該当設定の行番号を確認 # iptables -t nat -L PREROUTING --line-numbers 〜中略〜 4 DNAT tcp -- anywhere instance-data.ap-northeast-1.compute.internal tcp dpt:www to:172.16.105.32:8181 // 該当行を削除 # iptables -t nat -D PREROUTING 4
workerノード追加時にingressとポートが競合
ある時workerノードが追加された際に、kube2iamは問題なく展開されたのですが、ingressが起動しなくなるということがありました。
ちなみに環境としては、クラスターはRancher(v2.1.6)を使ってEC2上に構築しており、kube2iamもRancherのGUIからhelm(カタログ)でインストールしていました。
ingressのイメージはrancher/nginx-ingress-controller:0.16.2-rancher1
でした。
原因はkube2iamが使っている8181番ポートでした。
ingressは8181番ポートは実際には使わないようなのですが、起動時にこのポートのチェックを行い、使われていると起動しないようでした。
kube2iamはデフォルトで8181ポートを使うようになっており、worker追加時にkube2iamがingressより先に起動するとこのエラーに遭遇します。これは起動タイミングの問題で、先にingressが起動した場合は問題なくingressもkube2iamも起動します。
kube2iamは使用するポートを変更可能なので8182等にポートを変更してみましたが、やはり同じエラーに遭遇します。どうにも8181ポートを掴んでしまうようで。。
仕方ないので、kube2iamのdaemonset.yaml内で、initContainersの設定を追加しingressの起動を待つようにしました。(それまでhelmでインストールしていましたがここで諦めました)
initContainers: - name: ingress-check image: busybox command: ['sh', '-c', 'until wget --spider http://$(HOST_IP)/healthz; do echo waiting for ingress start; sleep 1; done;'] env: - name: HOST_IP valueFrom: fieldRef: fieldPath: status.podIP
IAMユーザーのアクセスキー
と、ここまでkube2iamを使ってpodやdeploymentごとに権限を渡す方法について書いてきましたが、他にもアプリケーションに権限を渡す方法はあります。
既にお気づきの方もいらっしゃると思いますが、IAMロールではなく、アプリケーションが必要な権限を付与したIAMユーザーのアクセスキーを発行し、それを各コンテナの環境変数として渡す方法があります。
これは非常にシンプルな方法で、kube2iamに依存しない分確実あるいは安定した方法とも言えるかもしれません。
ただし、そもそもAWSとしてはIAMロールをインスタンスプロファイルを使ってインスタンスに関連付ける方法を推奨しており、アクセスキーを使った方法は推奨していません。
個人的にもインスタンスプロファイルを使う方法に比べ、容易に持ち出し可能なアクセスキーを発行すること自体セキュリティリスクが増える印象ですし、インスタンスプロファイルを使う方法からアクセスキーを使う方法に変更することに若干の負け感も感じます。
アクセスキーは重要な情報ですから、発行すれば少なからずそれを管理・運用する手間が発生しますし、それを環境変数としてコンテナに渡す手順(Secretオブジェクトの作成等)も追加で必要になります。
筆者もkube2iamがiptablesを使っていることを知った時はちょっとした気持ち悪さと不安を覚え、アクセスキーの利用も考えたのですが、以上のような理由もあり、やはり使えるものなら今まで使っていたIAMロールをそのまま使いたいということで引き続きkube2iamを試しています。
まとめ
ピクスタのようにEC2で稼働するアプリケーションをコンテナ化してEC2上のk8sクラスターに移行しようとしている方もいらっしゃるかと思います。そしてここで紹介したような問題に遭遇しているのではないでしょうか?他の方がどのように対応したのか気になります。
筆者も現時点では最適解がどれか分からないのですが、この記事がkube2iamに関して使う/使わないの判断の一助になればと思います。