GeekFactory

int128.hatenablog.com

アプリケーションの開発フローとGitOps

アプリケーションの開発フローとKubernetesへのデプロイを考えた軌跡を残します。特に結論はないです。

前提

以下を前提とする。

  • Kubernetesにアプリケーションをデプロイする場合を考える。
  • すべての変更はPull Requestを通して適用する。
  • アプリケーションはソースコードを変更することなく、どの実行環境でも動作可能とする。
  • アプリケーションの設定は実行時に環境変数などで注入される。
  • 開発チームはアプリケーションと設定に責任を持ち、自分自身でデプロイを行う。
  • Platformチームは実行環境の健全性に責任を持つ。

以下はスコープ外とする。別に考える必要がある。

  • 実行環境自体のデプロイや変更(例えば、Kubernetesのバージョンアップ)
  • アプリケーションが依存するステートフルなリソース
  • データベーススキーママイグレーション
  • アプリケーションやイメージのセキュリティ検査
  • などなど

選択肢

以下の選択肢が考えられる。ここでは具体的なツールは考えない。

  1. ビルドとデプロイ
    1. アプリケーションのビルドとデプロイを分離する
    2. アプリケーションのビルドとデプロイを連続して行う
  2. ブランチ戦略
    1. tagから実行環境にデプロイする
    2. masterブランチから実行環境にデプロイする
    3. 実行環境ごとにブランチを用意する(例えば、GitLab Flow)
  3. イメージタグの管理
    1. イメージタグをリポジトリで管理する
    2. イメージタグはクラスタ内部でのみ管理する(リポジトリマニフェストには書かない)
  4. アプリケーションとマニフェスト
    1. アプリケーションとマニフェストを別のリポジトリで管理する
    2. アプリケーションとマニフェストを同一リポジトリで管理する

デプロイサイクルの速いチームは、(a-2)のようにブランチの更新を契機にアプリケーションをデプロイした方が無駄な作業が少なくなる。そうしないとdeveloper experienceが大幅に悪化するだろう。一方で、本番環境で問題が発生してすぐに旧バージョンに切り戻したい場合は、(a-1)のようにデプロイを切り離しておく方が早く確実に対応できる。

a, b, c, dには以下の制約がある。

  • アプリケーションのビルドとデプロイを連続して行う場合(a-2)
    • アプリケーションとマニフェストは同じリポジトリに入れる必要がある(d-2)。別のリポジトリにあるとアトミックな更新ができない。
    • 実行環境ごとにブランチを用意する必要がある(b-3)。単一のブランチではデプロイ先の実行環境を判断できない。

以降は具体的なツールを挙げて比較してみよう。

(a-1)(b-1)(c-1)(d-1)

GitOpsを採用する場合に最も簡単なフローから考えてみよう。デプロイの頻度が低いチームはこのフローで十分と思う。

  1. 開発者がアプリケーションリポジトリに新しいタグをpushする。
  2. CIが新しいイメージをビルドする。リポジトリのタグとイメージのタグは名前を合わせておく。
  3. 開発者がマニフェストリポジトリを更新するPRを作成する。
  4. 開発者がマニフェストリポジトリのPRをマージする。
  5. GitOpsツールがマニフェストリポジトリクラスタを同期させる。

メリデメ:

  • 👍 CI/CD外でリソースが変更されても不整合が起こらない。
  • 👍 旧イメージタグへの切り戻しが確実で早い。
  • 😱 デプロイに必要な作業が多すぎる。

(a-1)(b-2)(c-1)(d-1)

前項からdeveloper experienceを改善するためにイメージタグの更新を自動化する。イメージタグはcommit hashから生成する。

  1. 開発者がアプリケーションリポジトリのmasterブランチを更新する。
  2. CIが新しいイメージをビルドする。
  3. CIがマニフェストリポジトリのイメージタグを更新するPRを作成する。
  4. 開発者がマニフェストリポジトリのPRをマージする。もしマニフェストを変更したい場合はPRと合わせて更新する。
  5. GitOpsツールがマニフェストリポジトリクラスタを同期させる。

メリデメ:

  • 👍 開発者はPRをマージするだけで新イメージタグをデプロイできる。
  • 👍 CI/CD外でリソースが変更されても不整合が起こらない。
  • 👍 旧イメージタグへの切り戻しが確実で早い。
  • 😱 マニフェストリポジトリの更新やPRの作成を作り込むのが大変そう。

(a-2)(b-3)(c-1)(d-2)

前項ではPRを挟んでイメージタグを更新していた。ビルドとデプロイを連続して行いたい場合(a-2)、アプリケーションとマニフェストをアトミックに更新できないことが課題となる。そこで、アプリケーションとマニフェストを同じリポジトリで管理する(d-2)。

アプリケーションとマニフェストのライフサイクルが合わなくなるので、実行環境ごとにブランチを用意する(b-3)。

  1. 開発者がリポジトリの (master | staging | production) ブランチに対するPRをマージする。
  2. CIが新しいイメージをビルドする。アプリケーションリポジトリに含まれるマニフェストレンダリングする。
  3. CIがマニフェストリポジトリを更新する。
  4. GitOpsツールがマニフェストリポジトリクラスタを同期させる。

メリデメ:

  • 👍 開発者は実行環境に対応するブランチを更新するだけで新イメージタグをデプロイできる。
  • 👍 CI/CD外でリソースが変更されても不整合が起こらない。
  • 😱 切り戻す場合はアプリケーションのrevert commitが必要になる。
  • 😱 マニフェストの変更だけでもイメージが新しくなってしまう。
  • 😱 マニフェストリポジトリの更新やPRの作成を作り込むのが大変そう。

(a-2)(b-3)(c-2)(d-2)

GitOpsを使わない方法も考えてみよう。まずは、アプリケーションとマニフェストを同じリポジトリで管理する。

  1. 開発者がリポジトリの (master | staging | production) ブランチに対するPRをマージする。
  2. CIが新しいイメージをビルドする。
  3. CIがhelm upgradeを実行する。

もしビルドとデプロイを切り離したい場合(a-1)は、CIの手動ジョブを使えばよい。

メリデメ:

  • 👍 開発者は実行環境に対応するブランチを更新するだけで新イメージタグをデプロイできる。
  • 👍 CIで完結する。複数ツールのピタゴラスイッチがない。
  • 👍 緊急の切り戻しが必要な場合はリソースを直接変更できる。(直接変更できてしまうデメリットでもある)
  • 😱 新しいマニフェストにないリソースを確実に削除するため、Helmが必須になる。他のツールは使えない。
  • 😱 CI/CD外でリソースが変更されると不整合が発生する。
  • 😱 マニフェストの変更だけでもイメージが新しくなってしまう。
  • 😱 リポジトリクラスタの差分を確認することは可能(helm-diffを利用)だが、Argo CDのような分かりやすさはない。

(a-1)(b-2)(c-2)(d-1)

アプリケーションとマニフェストを別のリポジトリで管理する方法もあるが、GitOpsを利用する場合とほぼ同じフローで、わざわざHelmを利用するメリットは小さい。

まとめ

思いつくままに挙げていったところこんな感じです。誰かカルノー図で組み合わせ網羅をお願いします😓

ちなみに、EC2 + Auto Scaling Group + Immutable Infrastructureなサービスの開発チームにいた時は以下のフローでやっていました。

  1. 開発者がアプリケーションリポジトリに新しいタグをpushする。
  2. CIが新しいTar ballをビルドしてS3に配置する。
  3. 開発者がタグ名を指定してデプロイツールを実行する。(社内の内製ツール)
  4. デプロイツールがAuto Scaling GroupのLaunch Configurationを更新する。
  5. デプロイツールがEC2 Instanceをterminateする。

一方で、少人数で速いサイクルの開発を行っているチームでは以下のようなフローでした。

  1. 開発者がアプリケーションリポジトリのmasterブランチにpushする。
  2. CIが開発環境にデプロイする。
  3. 開発者が後続の手動ジョブを実行する。
  4. CIが本番環境にデプロイする。

というわけでオチは特にないです。

GitHub GraphQL APIで新しいブランチを作成する

GitHub GraphQL APIで新しいブランチを作成できるようになっていたので試してみました。本記事の内容はGraphQL API Explorerで実行できます。

リポジトリに新しいブランチやタグを作成するにはcreateRef mutationを利用します。createRefを実行するには以下の引数が必要です。

name (String!) The fully qualified name of the new Ref (ie: refs/heads/my_new_branch).

oid (GitObjectID!) The GitObjectID that the new Ref shall target. Must point to a commit.

repositoryId (ID!) The Node ID of the Repository to create the Ref in.

リポジトリID(repositoryId)とコミットID(oid)を取得するには以下のクエリを実行します。リポジトリ名やブランチ名は必要なものに置き換えてください。

query GetCommitID {
  repository(owner: "int128", name: "sandbox") {
    id
    ref(qualifiedName: "refs/heads/master") {
      target {
        oid
      }
    }
  }
}
{
  "data": {
    "repository": {
      "id": "MDEwOlJlcG9zaXRvcnk2OTgzMjM2Ng==",
      "ref": {
        "target": {
          "oid": "7a59fd9a6334706b942f1707531a0cf8a8523ce7"
        }
      }
    }
  }
}

クエリの実行結果からリポジトリIDは MDEwOlJlcG9zaXRvcnk2OTgzMjM2Ng==、コミットIDは 7a59fd9a6334706b942f1707531a0cf8a8523ce7 であることが分かります。これらのIDをcreateRefに渡します。

以下のmutationを実行するとexample1ブランチが作成されます。

mutation CreateBranch {
  createRef(input: {repositoryId: "MDEwOlJlcG9zaXRvcnk2OTgzMjM2Ng==", name: "refs/heads/example1", oid: "7a59fd9a6334706b942f1707531a0cf8a8523ce7"}) {
    clientMutationId
  }
}

REST APIの場合は1回のリクエストでブランチ作成を実行できますが、GraphQLの場合はqueryとmutationで2回のリクエストが必要です。

Kubernetes DashboardとAWS IAM認証

Amazon EKS #1 Advent Calendar 2019の13日目です。今日はKubernetes DashboardとIAM認証についてお話しします。

背景

Kubernetesクラスタをチームで運用する場合、チーム全員がコマンドラインツールに習熟しているとは限らないため、GUIツールを併用することが望ましいです。新しく参画したメンバはKubernetesの概念に不慣れかもしれません。初学者はコマンドラインツールとGUIツールを併用することで、新しい概念を理解しやすくなり、効率的に学習を進められます。

KubernetesGUIツールはいろいろありますが、まずは公式のKubernetes Dashboardを使ってみましょう。慣れてきたら別のGUIツールを探してみましょう。iOSのアプリもあります。

f:id:int128:20191212220309p:plain
Kubernetes Dashboard

課題

AWS公式チュートリアルでは、Service Accountを利用してEKSクラスタ上のKubernetes Dashboardにアクセスする方法が紹介されています。Kubernetes Dashboardを開くと下図のような入力画面が表示されるので、Service Accountのトークンを入力します。

f:id:int128:20191212220342p:plain
トークンの入力画面

チームでクラスタを利用する場合、Service Accountを共有すると以下のような課題があります。

  1. ユーザに応じた適切な権限付与ができない(アクセス制御)
  2. EKS Control Planeのログには同じService Accountが記録されるため、どのユーザが何の操作を行ったかを識別できない(ログ監査)
  3. 新規ユーザに安全な手段でトークンを共有する必要がある(Onboarding)
  4. ユーザがチームを離れた場合でもトークンが使えてしまう(Offboarding)

kubectlと同様にKubernetes DashboardでもIAM認証が使えると、これらの課題を解決できます。

本稿では、IAM認証を利用してKubernetes Dashboardにアクセスする方法を紹介します。

やってみよう

1. Kubernetes Dashboardをデプロイする

Kubernetes DashboardHelm Chartを利用すると簡単にデプロイできます。以下のコマンドを実行します。

helm install stable/kubernetes-dashboard --namespace kube-system --name kubernetes-dashboard

Helmを利用しない場合は、以下のManifestをデプロイします。

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

2. kauthproxyをインストールする

kauthproxyコマンドはHomebrewからインストールできます。

brew install int128/kauthproxy/kauthproxy

Krewというプラグインマネージャを利用している場合は、以下のコマンドでインストールできます。

kubectl krew install auth-proxy

3. Kubernetes Dashboardを開く

Kubernetes Dashboardを開くには以下のコマンドを実行します。

kubectl auth-proxy -n kube-system https://kubernetes-dashboard.svc

このコマンドを実行すると、自動的にブラウザが開いてKubernetes Dashboardが表示されます。もしブラウザが表示されない場合は http://localhost:18000 を開いてください。

Kubernetes Dashboardはログイン済みの状態で表示されます。トークンを入力する必要はありません。

動作原理

aws-iam-authenticator

EKS Control Planeにはaws-iam-authenticatorが組み込まれており、KubernetesのUser AccountとIAMのユーザを紐づける役割を果たしています。詳しくは7日目に id:katainaka0503 さんが書かれた記事が参考になります。

katainaka0503.hatenablog.com

EKSの推奨手順にしたがってkubeconfigを設定すると、kubectlコマンドを実行した契機でaws-iam-authenticatorが呼び出されます。aws-iam-authenticatorはSTSからトークンを取得し、標準出力を経由してkubectlコマンドにトークンを返します。kubectlコマンドはトークンを利用してEKS Control PlaneのAPIサーバにアクセスします。この一連の動作はEKS独自のものではなく、Kubernetesclient-go credential pluginsを利用しています。

詳しくは下記のスライドを参照ください。

speakerdeck.com

Kubernetes Dashboard

Kubernetes Dashboardは自身で認証の仕組みを持たず、HTTPリクエストに含まれるトークンをそのままAPIサーバに渡す設計になっています。したがって、aws-iam-authenticatorが取得したトークンをHTTPリクエストに付加することで、IAMユーザとしてKubernetes Dashboardにアクセスできます。

Kubernetes Dashboardの手前にリバースプロキシを入れると下図のようになります。

f:id:int128:20191212215909p:plain
Kubernetes DashboardKubernetes APIサーバの関係

ここまでくると、kauthproxyが何をやっているか想像がつくかもしれません。

kauthproxy

kauthproxyにはリバースプロキシとポートフォワードの機能があります。

f:id:int128:20191210103408p:plain
kauthproxyの構成

kauthproxyがやっていることは以下の3点です。

  1. aws-iam-authenticatorからトークンを取得する。
  2. Kubernetes DashboardのPodにポートフォワードする。
  3. リバースプロキシでHTTPリクエストのauthorizationヘッダにトークンを付加する。

kauthproxyはEKSに限らず利用できる汎用的なツールになっています。1ではclient-go credentials pluginからトークンを取得しているため、aws-iam-authenticatorだけでなくOpenID Connectでも動作します。また、authorizationヘッダを利用するアプリケーションであれば動作します。

まとめ

kauthproxyを利用すると、IAM認証を利用してKubernetes Dashboardにアクセスできます。

github.com