エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

入社一ヶ月でGKEのSecret管理基盤を作った話

こんにちは。エムスリーエンジニアグループAI・機械学習チームで2021年新卒の北川(@kitagry)です。最近買ってよかったものは低温調理器です。リモートワークとの相性が抜群です。

今回は僕が入社1ヶ月ほどで作成した、GKEのSecret管理基盤について書きたいと思います。

これはGKEのSecretを安全にかつ手軽に作成できるようにしたツールになります。 OSSになっているので皆さんのコントリビューションをお待ちしています。

この記事は5月始めに書こうとしましたが、書く内容をどうするかを考えている内に6月を過ぎてしまっていました。笑

AI・機械学習チームのサービスの特徴

AI・機械学習チームは検索基盤・記事レコメンド・メルマガのレコメンドなど数多くのプロダクトがあります。 しかし、今まで秘密情報の管理方法は決まっておらずCDのデプロイ時に秘密情報を埋め込んだり、KubernetesのSecretリソースを手動で作成するなどをしていました。 これらは今まで様々な問題がありました。 例えばCDでデプロイする際に秘密情報を埋め込む運用では、開発用のGKE上でしか起こらない問題をデバッグする時でさえ、いちいちコミットを行い、CIでテストが通るまで待ちCDでデプロイをしていました。 これらのサイクルはとても時間がかかり、開発のコストを上げてしまいます。 また、これらの管理方法が決まっていないことで、秘密情報を利用したい場合にどこにあるかの見通しが悪くなるなどの問題がありました。

そこで以下の2つの観点から秘密情報を管理する仕組みを作成しました。

  • 秘密情報へのアクセスを容易にする
  • 安全に保存する

Secretの管理方法の検討

Kubernetesで秘密情報を管理にはsopsやkubesecなど様々なツールがあります。 これらはyamlファイルに書かれた秘密情報を暗号化して、そのファイルごとコミットして使うようにしています。 これによって、ファイルをGitで管理できるためデプロイを安全かつ容易に行うことが出来ます。

しかし、この方法では人が暗号情報を用いたい場合にはそのリポジトリへ移動してファイルを復号化しなければなりません。 また、サービスが増えるたびに手動で暗号化をしてファイルをコミットする必要があり、とても時間がかかります。 これはプロダクトの数が多いAI・機械学習チームでは望ましいとは言えませんでした。

また、AI・機械学習チームではGKEのサービスも多くある一方でCloud RunなどのGCPプロダクトも使っています。 これらにはsopsのようなyamlファイルに秘密情報を保存する方法を使ってデプロイする方法は想定されていないと思います。 そのため、アクセスを容易にするという点においてsopsは求めている要素を少し満たさないのではないかと思いました。

これらの問題を解決するために、秘密情報をファイルとしてローカルに保存するのではなく、鍵も秘密情報もGCP上で保存する方法はないかと探しました。 そこで見つけたのが、GCP上での秘密情報の取得や保存のCLIやライブラリを提供しているberglasというツールでした。

berglasについて

berglasはGCP上に秘密情報を保存したり使用したりするための機能が提供されています。 暗号・復号鍵はCloud KMSを用いており、秘密情報の保存はGoogle Cloud Storage(GCS)またはSecret Manager storageを使用しています。

鍵の作成には以下のコマンドで作成できます。

// Secret Manager storage
berglas create sm://${PROJECT_ID}/foo my-secret-data

// gcs
berglas create ${BUCKET_NAME}/foo my-secret-data

また、以下のコマンドでアクセスできます。

// Secret Manager storage
berglas access sm://${PROJECT_ID}/foo

// gcs
berglas access ${BUCKET_NAME}/foo

これによって、秘密情報を安全に保存しつつも、人が秘密情報にアクセスするのがとても容易になりました。 また、berglasにはterraformのproviderもあり、これを使えばterraformで作成時に秘密情報を人の手を介することなくberglasで保存できます。 これによってヒューマンエラーの確率を少しでも下げることができます。

また、berglasのリポジトリにはGAEやGKEなどで動かすサンプルがあり、GCP上の様々なプロジェクトでberglasを使うことができます。 このブログではGKEでberglasを動かす方法について詳しく書いていきます。

berglas on GKE

berglasにはk8sで使うためのサンプルがあります。 最初はこの手順を使ってberglas on k8sを実行しようとしました。 これはMutating Webhookを使用したもので、以下のようなPodが作成された場合にberglasコマンドを使うようなinit containerを差し込むという物です。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo $SECRET']
    env:
    - name: SECRET
      value: berglas://path_to/secret

差し込まれたあとはこのようになります。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  initContainers:
  - name: copy-berglas-bin
    image: us-docker.pkg.dev/berglas/berglas/berglas:latest
    command: ['sh', '-c', 'cp /bin/berglas /berglas/bin/']
    volumeMounts:
    - name: berglas-bin
      readOnly: false
      mountPath: /berglas/bin
  containers:
  - name: myapp-container
    image: busybox
    command: ['/berglas/bin/berglas']
    args: ['exec', '--', 'sh', '-c', 'echo $SECRET']
    env:
    - name: SECRET
      value: berglas://path_to/secret
    volumeMounts:
    - name: berglas-bin
      readOnly: true
      mountPath: /berglas/bin
  volumes:
  - name: berglas-bin
    emptyDir: {}

少し説明すると、initContainerで共通のvolumeに対してberglasのCLIを保存します。 そして、berglasの環境変数を持つ全containerのcommandをvolumeに保存したberglasコマンドを通して行うことによって、環境変数にberglasのURIを持つものがあれば書き換えるという処理を行います。

このように行うことによって、各Podへberglasへのアクセス権限さえ付与すれば簡単に秘密情報を使うことができます。

しかし、このMutating Webhookを用いた方法には以下のような問題点があります。

1. Webhookの可用性

Webhookが何かの問題で動かない場合には環境変数が読み込めずに、Podが起動できないということが考えられるます。 また、起動できないのはまだ良くて、環境変数が書き換わらないまま動き続ける場合も考えられます。 上の問題を解決するためにWebhookのfailure Policyを厳しく設定した場合もberglasに関係ないPodまで起動が妨げられるということが考えられます。 そのため、Webhookの可用性やセキュリティなどの担保がかなり要求されます。

2. IAMポリシーの複雑さ

この方法を使うためには各PodのIAMポリシーでberglasを取得するための権限を付与しなければなりません。 権限の付与は以下のように行えます

berglas grant ${BUCKET_ID}/foo --member user:[email protected]

しかし、これらは弊チームのようなサービスが数多くあるチームには煩雑な作業になります。

berglas-secret-controller

そこで、上のMutating Webhookの問題を解決するためのK8sのカスタムコントローラーを作成しました。

github.com

これは以下のようなカスタムリソースを用います。

apiVersion: batch.kitagry.github.io/v1alpha1
kind: BerglasSecret
metadata:
  name: berglassecret-sample
  namespace: default
spec:
  data:
    username: berglas://bucket/username
    password: berglas://bucket/password

このカスタムリソースを作成すると、controllerが同じ名前の以下のようなSecretを作成します.

apiVersion: v1
kind: Secret
metadata:
  name: berglassecret-sample
  namespace: default
spec:
  data:
    username: <secret>
    password: <secret>

Podからこの秘密情報を用いる場合は、envFromでこのSecretを指定すれば良いです。 これによって先ほどのMutating Webhookの可用性とIAMポリシーの管理についての問題が解決されました. これらを一つずつ見ていきたいと思います。

1. berglas-secret-controllerの可用性

berglas-secret-controllerが落ちている場合にはSecretが作成されないので、Podは起動できません。 そのため、Podが誤った秘密情報を用いて動くことを防ぐことが出来ます。 また、berglasの秘密情報を使わない場合はこのberglas-secret-controllerが動かなくても全く問題なく動作します。

2. IAMポリシーの管理

IAMポリシーについてはberglas-secret-controllerに秘密情報のアクセス権限があれば各Podに権限を与えなくてもよくなります。 これで煩雑なポリシーの設定から開放されます。

ただし、これは秘密情報がberglas-secret-controllerに集約されることになるのでk8sへのアクセス権限などは気にするべきだと思います。 弊チームでもberglas-secret-controllerを導入する際にはこのポイントについて議論しました。

運用フロー

ここでは実際の運用フローを簡単に説明していきたいと思います。

弊チームではterraformを使ってインフラ基盤の管理をしています。 そして、terraformでDBのパスワードなども作成したりしています。 以下のように書くことによって、パスワードをランダムに生成し、生成したパスワードをberglasによって暗号化してGCSに保存できます。

resource "random_string" "database_password" {
  length  = 10
  special = true
}

resource "berglas_secret" "db_pass" {
  bucket = var.bucket_name
  key = "projects/${var.project}/locations/global/keyRings/berglas/cryptoKeys/berglas-key"
  name = "DATABASE_PASSWORD"
  plaintext = random_password.database_password.result
}

実際にアプリケーションからパスワードを使用する場合はberglas-secret-controllerを使って以下のように指定します。

apiVersion: batch.kitagry.github.io/v1alpha1
kind: BerglasSecret
metadata:
  name: berglassecret-sample
spec:
  data:
    password: berglas://BUCKET_NAME/DATABASE_PASSWORD
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample
spec:
  template:
    spec:
      containers:
      - name: manager
        envFrom:
        - secretRef:
            name: berglassecret-sample

これで秘密情報をアプリケーションで使用できました。 見てもらえればわかるように、Deploymentでパスワードを使うために人がパスワードを見る必要がありません。 人が触らないことによってヒューマンエラーが発生するのを少しでも防ぐことができたと思います。

最後に

弊チームではberglasを導入することによって以下のような利点を得ることができました.

  • 秘密情報管理の一元化
    • 秘密情報がどこにあるのかがわかるようになる
  • ポータビリティの向上
    • sopsのようにファイルに保存したり、Gitlabに保存するのではなくクラウド上に保存される
    • CLIã‚„k8sからアクセスが可能になる
  • ヒューマンエラーの削減
    • terraformで作成する秘密情報の場合、実際の秘密情報を人が見る必要はありません

また、berglas-secret-controllerを作成することによって、k8s上での秘密情報管理を容易にすることが出来ました。 こちらはまだまだ発展途上なのでコントリビューションお待ちしてます!

僕はKubernetes周りのツール作成に興味があるので、今後もこのような便利なツールが作れればいいなと思っています。

We're hiring !!!

エムスリーでは機械学習を用いた数多くのプロダクトが生み出されています。 そのプロダクトを支えるインフラ基盤を一緒に作ってくれる仲間を募集しています!

「ちょっと話を聞いてみたいかも」という人はこちらから!

jobs.m3.com