dd-agent の EKS on Fargate 対応が何をしているかざっくり調べる
dd-agent の EKS on Fargate 対応について、どのように動作しているか、少し興味があったのでざっくり調べました。
dd-agent の EKS on Fargate 対応は、現在(2020/01) はベータという位置付けです。 ドキュメントは、この辺りになります。
dd-agent コンテナは、サイドカーとして動かします。また、Pod に割り当てる ServiceAccount には、次のような権限が必要になるようです。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: datadog-agent rules: - apiGroups: - "" resources: - nodes/metrics - nodes/spec - nodes/stats - nodes/proxy - nodes/pods - nodes/healthz verbs: - get
Node の API リソースへアクセスできる必要があります。
また、コンテナに環境変数として、 DD_KUBERNETES_KUBELET_NODENAME
というものを定義する必要があります。
- name: DD_KUBERNETES_KUBELET_NODENAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName
dd-agent の Github リポジトリに、eks-fargate-beta
というブランチがあります。この差分をみながら、何をしているか調べてみます。
通常の DaemonSet として各ノードに agent を配置する際は、直接 kubelet に対してリクエストを行いノード内のメトリクス収集を行うようですが、Fargate の際は api-server からノードへプロキシして、ノード内のメトリクス収集を行っているように見えます。
+ if ku.kubeletProxiedEndpoint && config.Datadog.Get("kubernetes_kubelet_nodename") != "" { + ku.kubeletApiEndpoint = fmt.Sprintf("https://%s:%s/api/v1/nodes/%s/proxy/", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT"), config.Datadog.Get("kubernetes_kubelet_nodename")) + log.Infof("EKS on Fargate mode detected, will proxy calls to the Kubelet through the APIServer at %s", ku.kubeletApiEndpoint) + return nil + }
api/v1/nodes/{node_name}/proxy/
というパスに対してリクエストすることで、対象ノードの kubelet へのリクエストをプロキシできるということでしょうか。
このURLに対して、手で curl を叩いて実際に情報が得られるか試してみます。
次のような感じの Pod を用意します。
apiVersion: v1 kind: Pod metadata: name: test-myapp namespace: myapp spec: serviceAccountName: myapp containers: - name: test image: debian env: - name: KUBELET_NODENAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName command:["tail", "-f", "/dev/null"]
コンテナに入ります。
kubectl apply -f test-pod.yaml kubectl exec -it test-myapp sh
そして次のような curl コマンドを実行すると確かに情報が得られます。
curl \ --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ "https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/nodes/${KUBELET_NODENAME}/proxy/pods" curl \ --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ "https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/nodes/${KUBELET_NODENAME}/proxy//metrics"
以上です。
まとめ
まだあまり Kubernetes におけるモニタリング、 kubelet や Kubernetes API の仕様・挙動には詳しくないですが、動作を追うことで少し理解が進みました。
dd-agent の EKS on Fargate 対応で、実際に十分な情報が収集できるのかはまだ検証できていないので、これから行っていこうと思います。
Kubernetes Mutating Webhook で、動的に秘匿情報を注入する
昨年、後半あたりから仕事でも Kubernetes に触れる機会が増えてきました。Kubernetes には、自らプログラミングすることによって、振る舞いを拡張する手段が用意されており、面白いなと感じています。
そんななかで、Kubernetes で動かすアプリケーションの秘匿情報の管理について、どのようにしようかと少し考えていました。秘匿情報というのは、データベースのパスワードであったり、外部サービスのAPIキーのようなものを想定しています。Kubernetes には、Secrets リソースがあり、シンプルに秘匿情報を扱えますが、いくつかの問題から、実際の運用においては、 Secrets リソースをそのまま使うのではなく、様々なアプローチが取られる場合もあるように思います。
今回は、Admission Webhook そのなかでも Mutating Webhook を用いて、秘匿情報を扱うためツールを書いてみました。 この記事は、二つのトピック、Kubernetes における秘匿情報管理、また、Mutating Webhook でどういったことができるか、を含みます。あまりまとまった内容になっていないと思いますが、参考になればと思います。
Mutating Webhook
Kubernetes には、Admission Webhook というものがあります。 Admission Webhook では、リソース操作のリクエストに対して、Mutation(変更) と Validation(検証) を行うための独自のWebhook を定義することができます。それぞれ、 Mutating Webhook 、 Validating Webhook と呼ばれます。
次の図を見てもらうのが、わかりやすいと思います。
この仕組みを用いて、クラスタ管理者は、セキュリティ・ガバナンス・構成管理などにおいて、独自の仕組みを提供することができます。
Istio など Service Mesh フレームワークでは、よく Sidecar Injection の機能があると思いますが、それは Mutating Webhook によって実現されています。 必ずしも自身で Webhook を実装するケースは多くはないと思いますが、このような仕組みがあることを知っておくのは良いと思います。
さて、つぎは、Kubernetes における秘匿情報の様々な管理方法について紹介します。そこでも Mutating Webhook を用いられるケースがあります。
Kubernetes における秘匿情報管理のアプローチ
普通、クラスタに適用するリソースのマニフェストは、 Git でバージョン管理していると思います。 Secrets リソースもマニフェストで定義できますが、その中身は平文であるため、そのままではコミットすることは望ましくありません。
また、GKE や EKS などのマネージドサービスで、クラスタを運用する場合も多いと思いますが、その場合はそれぞれのクラウドプロバイダが提供する暗号化サービスや秘匿情報データベースサービスとうまく連携できると良さそうに感じます。
たとえば、 AWS ECS では、 Parameter Store に保存された秘匿情報をコンテナ実行時に環境変数として引き渡すことができます。このような機能があると、コンテナ側からは、秘匿情報を環境変数としてシンプルに扱える上、管理の上でも、Parameter Store/KMSによる権限管理・監査ログなどの機能が使えます。
Kubernetes 上で秘匿情報を扱うためのツール・コントローラなどを調べてみると、次のような手法がありました。それぞれについて、簡単にどのように動作するかを記載しています。
1. kubesec
- Secrets マニフェストを暗号化して Git にコミットする
2 kustomize secretGeneratorPlugin
kustomize build
実行時に動的に外部から秘匿情報を取得して、マニフェストを生成する
3. godaddy/kubernetes-external-secrets
- ExternalSecret というカスタムリソースを作成する
- ExternalSecret リソースは、AWS Secrets Manager や Hashicorp Vault から秘匿情報を取得して、Secrets リソースを生成する
- Pod からは、生成された Secrets を参照することで、秘匿情報にアクセスする
4. Mutating Webhook による Secrets の置き換え
- Admission Webhookで快適なSecret管理
- Secrets 内に特別なプレフィクス
berglas://
を持ったデータがある場合、Mutating Webhookが復号して書き換える
5. hashicorp/vault-k8s
- Pod に使用したい秘匿情報をアノテーションとして記載しておく
- Mutating Webhook が Sidecar Container を挿入する
- Sidecar Container は秘匿情報を Vault から取得して共有ボリュームに書き込む
6. banzaicloud/bank-vaults Mutating Webhook
- Mutating Webhook は、InitContainer を追加する
- InitContainer は、秘匿情報を取得するためのツールのバイナリを共有ボリュームにコピーする
- アプリケーションをコンテナは、Mutating Webhookによって entrypoint を書き換えられており、コピーされたバイナリが実行される
- コピーされたバイナリは、環境変数に特別なプレフィクス
vault:
がついたものがあった場合、 Vault から秘匿情報を取得して値を置き換えた上で、元のコマンドを実行する
1 の kubesec は、クラスタ側に何かを導入する必要はなく、秘匿情報をシンプルに扱えます。これで十分なケースも多いのではないかと思います。マニフェストは復号した上で適用する必要があるため、 Gitops のような Pull 型のデプロイ手法をとっている場合はすこし難しさがありそうです。
2 は kustomize を使うのが前提になってしまいますが、こちらもマニフェストの適用前に、外部の秘匿情報データベースなどを利用して秘匿情報を取得した上で適用します。
3 と 4 の手法は、仕組みも直感的で、スマートに外部の秘匿情報データベースと連携がおこなえます。ただし、Secrets に復号された秘匿情報が書き込まれるので、それも避けたいケースでは合いません。また、コントローラが全てのアプリの秘匿情報を復号するため、そこに権限が集中するのもやや気になりました。
5 と 6 の手法は、ややトリッキーにも思えましたが、Pod実行時に直接復号するため、その点ではセキュアな方法のように思えます。
今回は、自身の練習を兼ねて、6 の banzaicloud/bank-vaults を真似たツールを作成しました。banzaicloud/bank-vaults は対象が Hashicorp Vault のため、普段私がよく使う AWS Parameter Store あるいは Secrets Manager を扱えるようなものとしました。
作ったもの
作ったものは、こちらにおいてあります。
(とりあえず、動くところまで作ってみたという段階なので、実用には耐えるものではないと思います。)
Go で書かれていますが、2つのコマンド cloud-secrets controller
cloud-secrets exec
からなります。
前者を実行すると、Mutating Webhook のサーバが起動します。後者は、秘匿情報を取得した上で、与えられた任意のコマンドを実行するシンプルなコマンドです。
まずは、 cloud-secrets exec
について、説明します。
実は、 cloud-secrets exec
はそれ単体でも使うことができます。
似たようなものに、 chamber というツールの exec コマンドや、envconsul などがあります。これらは、指定した秘匿情報を取得し、環境変数にセットした上で与えられた任意のコマンドを実行します。
cloud-secrets exec
は、次のように使うことができます。
$ export FOO=cloud-secrets://aws-parameter-store/my-secrets/foo $ cloud-secrets exec sh -c 'echo $FOO' FOO_SECRET_VALUE
cloud-secrets://
というプレフィクスがついた環境変数を見つけると、指定されたパスの秘匿情報を取得して、値を置き換えてコマンドを実行します。
一方、 cloud-secrets controller
は、Mutating Webhook のHTTPサーバです。これをクラスタにデプロイすると、Pod リソースを書き換えを行う Mutating Webhook として機能するようになります。
どのような書き換えを行うかというと、各コンテナの entrypoint を書き換えて、さきほどの cloud-secrets exec
を通して元の コマンドを実行するようにします。ただし、cloud-secrets のバイナリ は各コンテナにはインストールされていないはずなので、もう一つの工夫として、InitContainer の挿入を行います。この InitContainer は、コンテナ内の cloud-secrets のバイナリ を emptyDir ボリュームにコピーするだけです。そのコピーされたバイナリを各コンテナは entrypoint として使用します。
ユーザとしては、次のような Pod のマニフェストをデプロイすると、秘匿情報をアプリケーションに引き渡すことが可能になります。
apiVersion: v1 kind: Pod metadata: name: myapp annotations: cloud-secrets.daisaru11.dev/enabled: "true" spec: containers: - name: myapp image: myapp env: - name: FOO value: "cloud-secrets://aws-parameter-store/myapp-secrets/foo"
普通のPodマニフェストと異なるのは、この2点です。
以上が、今回作成してみたツールの大まかな仕組みになります。
この仕組みは、ほぼ banzaicloud/bank-vaults を参考にしており、そちらの紹介記事も合わせて参考にするとわかりやすいかもしれません。
Mutating Webhook を実装する際に参考にしたもの
今回初めて Mutating Webhook を実装してみましたが、比較的簡単に実装を行えました。Mutating Webhook 自体は単なる HTTP サーバであり、期待した形式でレスポンスを返してあげればよいです。
とはいえ、最初はどこから手をつけて良いかわからなかったので、他のOSSの実装、特に、vault-k8s と banzaicloud/bank-vaults を参考にしながら実装しました。
banzaicloud/bank-vaults は、 kubewebhook という Admission Webhook を書くためのフレームワークを使っているようでした。これを用いると、Mutation の部分の処理のみ実装すれば良く、少し楽ができるようです。とはいえ、使わなくてもそれほど大変というわけでもなく、私は今回使わずに実装を行いました。
そのほか、少しわかりづらかったのはTLS証明書の生成についてです。Webhook と apiserver との通信は HTTPS で行われるため、証明書が必要です。実際の運用では、cert-manager を使って管理することになりそうですが、とりあえず動かすことが目的であれば cfssl というツールを使って発行するのが楽なようです。作り方はこちらを参考にさせてもらいました
終わりに
今回は、秘匿情報の管理について、どのようなアプローチがあるか調べてみました。また、 Mutating Webhook を実装することで、秘匿情報の展開を行う独自ツールの作成を行いました。
作ったツールは、実際に仕事で使うかどうかは微妙なところかなと思っています。 Admission Webhook は導入すると運用が発生しますし、あまり独自の仕組みを作るとシステム全体の見通しが悪くなります。kubesec など、シンプルなツールで十分なケースはそちらを使おうと思っています。
ただやはり、 Admission Webhook は強力な仕組みであり、Kubernetes を基盤として提供する上では、有効に使っていきたいと考えています。最近、 EKS Fargate を触っているのですが、 Fargate では DaemonSet が使えないため、ロギングやメトリクス収集といった横断的な機能は Sidecar で行う必要があります。そういったものを統一的に提供するには、 Mutating Webhook を用いて Sidecar Injection が有効なのではないかと考えおり、挑戦してみたいと思っています。