Azure で GitOps 環境を超速で作ってみる (Flux + bedrock + terraform)
こんにちは。ポッケのシステムグループの @kumagaias です。
2020 年 1 月に GitOps をテーマにした Microsoft 様とのハッカソンに参加させて頂きました。
その時の内容の一部を少しアレンジし、メモ程度ですが公開させて頂きます。
1. 前置き
1.1 GitOps って?
SRE チームの @kano_k6a さんが作成したスライドがありますので宜しければご参照ください。
https://speakerdeck.com/kanok/challenge-gitops-using-azure-devops
何が変わるか?
私が今居るプロジェクトの開発環境のデプロイの例でお話しますと・・
GitOps 導入前
デプロイするにはまず jumpbox を起動し、デプロイしたい環境 (sandbox, staging ...) のサービスのマニフェストを手で変更した後、Pod を再起動するためにコマンドを叩いて回っていました。
手作業が多く、普段マニフェストを触っていないアプリ開発者はデプロイするのに毎回 30 分くらいかかっていました。
また、なぜか結合テストが上手く行かなかったりして、上記の記述ミスやデプロイ漏れが原因とわかるまで 1 - 2 時間費やす・・ということも 1 週間に 1 回くらいありました。
GitOps 導入後
開発環境は常に Git リポジトリと同じ内容であることが保証されております。したがって、開発者は最新版が上がっているかどうかを気にする必要がなくなりました。
参考: What is GitOps?
1.2 Flux って?
GitOps を Kubernetes 上で行ってくれるエージェントです。マニフェストの Git リポジトリをポーリングします。
参考: Flux
1.3 bedrock って?
GitOps 環境を Azure に作成できる terraform のテンプレート集です。Microsoft の方がメンテナンスされています。
参考: bedrock
1.4 terraform って?
terraform 様式で宣言的に書かれた定義ファイルを元に、クラウドにリソースを作成・削除できるツールです。
参考: terraform
2. 準備
2.1 ツールのインストール
Mac の方は brew でサクッと入れてしまいましょう。
$ brew update && brew install azure-cli terraform helm@2
brew でバージョン指定して install した場合はパスが通っていないので、通します。
$ echo 'export PATH="/usr/local/opt/helm@2/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
$ helm version
Client: &version.Version{SemVer:"v2.16.1", GitCommit:"bbdfe5e7803a12bbdf97e94cd847859890cf4050", GitTreeState:"clean"}
Mac で brew を使わない場合や Windows や Linux の方は、下記ページから環境に合わせてインストールしましょう。
ハマリポイント 1:
helm は必ず version 2 を!
helm version 3 だと terraform apply 時に以下のエラーがでます。bedrock が対応していないためです。※ 執筆時点
W0122 22:28:41.365622 64284 loader.go:223] Config not found: ./output/bedrock_kube_config
Error: unknown flag: --name
ERROR: failed to helm template
2.2 Azure アカウント作成
Azure で作成しましょう。
クレジットカードの登録が必要ですが、従量課金のプランに変更しない限りは課金されません。また、最初の 30 日間利用できる ¥22,500 クレジットも提供されます。
2.3 Azure DevOps にリポジトリ作成
マニフェストを置くリポジトリを作成します。
今回は Azure DevOps を使用していきます。もちろん GitHub でもできると思います。
組織名を聞かれます。任意の名前で大丈夫です。
ここでは pocke-tech-blog としました。
リージョンは East Asia のままで良いと思います。
続いて、プロジェクト名を聞かれます。
ここでは pocke-tech-blog-project としました。
公開設定は Private を選択しておきましょう。
プロジェクトができたらリポジトリを作ります。
1. 左下の「Project settings」をクリック。
2. 左メニューの「Repositories」をクリック。
3. 真ん中の「New repository」をクリック。
4. Repository name を入力。ここでは azure-vote としました。
5. 「Create」をクリック。
これで完了です。
ちなみに下記のように az で azure-devops という拡張を入れると、プロジェクト作成とリポジトリ作成は CLI からできます。
$ az extension add --name azure-devops
$ az login
$ az devops project create --name pocke-tech-blog-project --organization=https://dev.azure.com/pocke-tech-blog/
$ az repos create --name azure-vote --organization=https://dev.azure.com/pocke-tech-blog --project pocke-tech-blog-project
2.4 Azure DevOps に公開鍵を設定
2.3 のリポジトリを clone するために、Azure DevOps に公開鍵を設定します。
まずはローカルの公開鍵をコピーします。
$ cat ~/.ssh/id_rsa.pub
ローカルに鍵が無い場合は作成します。
$ ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -C “gitopsdemo@example.com”
$ cat ~/.ssh/id_rsa.pub
次は Azure DevOps を開き、右上の人のアイコンをクリックし、「SSH public keys」をクリックします。
「Add」をクリックします。
4. Description に任意の名前を入力 (ファイル名と合わせる必要無いです。)
5. Key Data に先ほどコピーした公開鍵をペースト。
6. 「Save」をクリック。
参考: Use SSH key authentication
2.5 Azure DevOps のリポジトリを clone
URL まで辿り着くのが初見だと難しかったので、この章もキャプチャ多めです (画面すぐ変わっちゃいますが・・。)
1. 左メニューで「Repos」をクリック。
2. 上メニューのプルダウンをクリック。
3. 対象のリポジトリを選択。
4.「Clone」をクリック。
5. SSH を選択。
6. コピー。
これでやっと git clone できます。
$ git clone git@ssh.dev.azure.com:v3/pocke-tech-blog/pocke-tech-blog-project/azure-vote
Azure DevOps は多機能が故に、git clone するまでの道のりがちょっと遠いですね。
3. 本編
ここからが本編です。お待たせしました。
3.1 マニフェストを push
2.5 で作成したマニフェストリポジトリに移動します。まだ何も commit されていない状態です。
$ cd azure-vote
今回は Azure で用意してくれているサンプルアプリ azure-vote を使います。投票アプリのようです。下記から YAML をコピーします。
クイック スタート:Azure CLI を使用して Azure Kubernetes Service クラスターをデプロイする
azure-vote.yaml というファイルを作成して、上記をペーストします。
$ vi azure-vote.yaml
commit して push します。
$ git add azure-vote.yaml
$ git commit -m 'add azure-vote.yaml'
$ git push
3.2 Azure のリソースグループ、サービスプリンシパルを作成
pocke-tech-blog-group という名前でリソースグループを作成しました。
$ az group create -n pocke-tech-blog-group -l japaneast
{
"id": "/subscriptions/f328ed82-6304-494d-93bc-c7b08e61aeb4/resourceGroups/pocke-tech-blog-group",
"location": "japaneast",
"managedBy": null,
"name": "pocke-tech-blog-group",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
}
上記の "id" をコピーします。(/subscriptions/...)
続いてサービスプリンシパルを作成します。サービスプリンシパルとは、「Azure のリソースを操作するアプリケーションのための ID」です。
参考: 3分でわかるAzureでのService Principal
--scopes の後に上記の "id" をセットします。
$ az ad sp create-for-rbac --role contributor --scopes /subscriptions/f328ed82-6304-494d-93bc-c7b08e61aeb4/resourceGroups/pocke-tech-blog-group
Creating a role assignment under the scope of "/subscriptions/f328ed82-6304-494d-93bc-c7b08e61aeb4/resourceGroups/pocke-tech-blog-group"
Retrying role assignment creation: 1/36
{
"appId": "XXXXXXXXXX", // service_principal_id
"displayName": "azure-cli-2020-01-22-12-39-58",
"name": "http://azure-cli-2020-01-22-12-39-58",
"password": "XXXXXXXXXX", // service_principal_secret
"tenant": "b69a2371-2e59-4a77-8f89-28f2f860e03c"
}
上記結果が 3.4 で必要になります。
"appId" が bedrock では service_principal_id
"password" が bedrock では service_principal_secret
となります。
3.3 Flux 用の鍵を作成
Flux が azure-vote リポジトリをポーリングするための鍵を作成します。
いったん ~/.ssh/gitops-ssh-key に作成しました。
$ ssh-keygen -t rsa -f ~/.ssh/gitops-ssh-key -N "" -C “gitopsdemo@example.com”
「2.4 Azure DevOps に公開鍵を設定」と同じ要領で、公開鍵を Azure DevOps に登録します。下記の出力を Azure DevOps に登録します。
$ cat ~/.ssh/gitops-ssh-key.pub
秘密鍵の絶対パスをコピーしておきます。
$ find ~/.ssh/gitops-ssh-key
/Users/hoge/.ssh/gitops-ssh-key
3.4 bedrock を clone しカスタマイズ
さて!いよいよ bedrock を clone します。
bedrock はテンプレート集で、必要なものをコピーしてもってくる感じです。今回は bedrock/cluster/environments/azure-simple を使いますので、適当な場所にコピーします。git-ops というフォルダ名にしてみました。
$ git clone git@github.com:microsoft/bedrock.git
$ cp -r bedrock/cluster/environments/azure-simple git-ops
$ cd git-ops
変数ファイルの terraform.tvars をカスタマイズします。各パラメーターの詳細は GitHub を見ると良いです。
参考までにこんな感じでやりました。
$ cat terraform.tfvars
resource_group_name = "pocke-tech-blog-group" # 先ほどつくったリソースグループをセット。
cluster_name = "pocke-tech-blog-cluster" # 任意のクラスター名
agent_vm_count = "3"
dns_prefix = "pocketechblogdns" # 任意の DNS プレフィックス名
service_principal_id = "XXXXXXXXX" # サービスプリンシパルの "appId" です。
service_principal_secret = "XXXXXXXXX" # サービスプリンシパルの "password" です。
ssh_public_key = "ssh-rsa XXXXXXXXX \"gitopsdemo@example.com\"" # Flux 用の公開鍵をそのままペースト
gitops_ssh_url = "git@ssh.dev.azure.com:v3/pocke-tech-blog/pocke-tech-blog-project/azure-vote" # ssh url to manifest repo
gitops_ssh_key = "/Users/hoge/.ssh/gitops-ssh-key" # Flux 用の秘密鍵の <<パス>> を指定
vnet_name = "pocke-tech-blog-vnet" # 任意の vnet 名
agent_vm_size = "Standard_DS1_v2" # 追記。VM の種類を指定。
#--------------------------------------------------------------
# Optional variables - Uncomment to use
#--------------------------------------------------------------
# gitops_url_branch = "release-123"
# gitops_poll_interval = "30s"
gitops_path = "azure-vote.yaml"
# network_policy = "calico"
# oms_agent_enabled = "false"
# gitops_label = "custom-flux-sync"
ハマリポイント 2:
gitops_ssh_key は Flux 用の秘密鍵のパスを指定します。ここでは絶対パスで /Users/hoge/.ssh/gitops-ssh-key と指定しました。
ハマリポイント 3:
variables.tf の default の値を変更するやり方もありますがここでは terraform.tvars の方を編集します。同じ変数が両方に存在した場合、terraform.tvars の値で上書きされます。
ハマリポイント 4:
Azure 無料アカウントの場合はサービスのクォータと制限に引っかかります。 (exceeding quota limit of Core)
Error: Error creating/updating Managed Kubernetes Cluster "git-ops-cluster" (Resource Group "pocke-tech-blog"): containerservice.ManagedClustersClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="QuotaExceeded" Message="Provisioning of resource(s) for container service git-ops-cluster in resource group pocke-tech-blog failed. Message: The operation couldn't be completed as it results in exceeding quota limit of Core. Maximum allowed: 4, Current in use: 0, Additional requested: 6. Read more about quota limits at https://aka.ms/AzurePerVMQuotaLimits. Submit a request for Quota increase using the link https://aka.ms/ProdportalCRP/?#create/Microsoft.Support/Parameters/%7B%22subId%22:%22f328ed82-6304-494d-93bc-c7b08e61aeb4%22,%22pesId%22:%2206bfd9d3-516b-d5c6-5802-169c800dec89%22,%22supportTopicId%22:%22e12e3d1d-7fa0-af33-c6d0-3c50df9658a3%22%7D.. Details: "
なので VM のスペックを落としてやります。今回は terraform.tvars に下記を追記してやると、agent_vm_count = "3" でもデプロイできました。(VM のスペックはそのままで agent_vm_count = "1" にしてやっても大丈夫ですが、せっかく Kubernetes を使うので VM を複数使いたいですしね。)
agent_vm_size = "Standard_DS1_v2" // CPU core 1 の VM
ちなみに従量課金制に移行した場合は制限を上げるリクエストを送れるのですが、無料アカウントの場合はできないようです。
無料試用版サブスクリプションはクォータ引き上げの対象ではありません。
さて、少しだけ main.tf を覗いてみます。
例えば 5 - 7 行目ですが、
data "azurerm_resource_group" "cluster_rg" {
name = "${var.resource_group_name}"
}
name に "${var.resource_group_name}" を代入しています。
これは terraform.tvars で設定した resource_group_name、すなわち "pocke-tech-blog-group" が代入されます。
つまり main.tf 自体にはベタ書きせず variables.tf に変数の型やデフォルト値を、terraform.tvars に環境情報を書いていく感じでしょうか。
3.5 terraform でデプロイ
さてさて!いよいよデプロイ作業です。terraform の出番です!
3.4 で作業した git-ops ディレクトリで terraform init で初期化。
$ terraform init
terraform plan で変更内容や error を確認。
$ terraform plan
初めての場合はここで結構 error が出ます・・。
error を 1 個 1 個潰して行きましょう。warning はいったん無視しても大丈夫だと思います。
最後に terraform apply でデプロイ実行です。
$ terraform apply
途中で confirm が挟まれるので yes と入力します。
クラスター作成まで 20 - 30 分くらいかかるのでビールでも飲んで待ちましょう。🍺
3.6 デプロイ確認
ビールが空になったところで、apply の結果が返ってきていると思います。
もしエラーになっていても気を落とさずに・・。ハッカソンでも何回もエラーになりました。
落ち着いてエラーメッセージを確認し、terraform.tvars の内容や、鍵の設定周りをもう一度確認しましょう。
もし apply が無事に完了した場合はクラスターが作成されていますので、接続情報をダウンロードします。
これでローカルで kubectl を叩くと作成したクラスターに接続できるようになります。(なので、クラスターを再作成した場合は毎回下記が必要です。)
$ az aks get-credentials --resource-group pocke-tech-blog-group --name pocke-tech-blog-cluster
次に kubectl get pods --all-namespaces -o wide を叩いて flux の Pod が居るか確認しましょう。
$ kubectl get pods --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
flux flux-96477f6dd-ffcwr 1/1 Running 0 28m 10.10.1.80 aks-default-31698870-0 <none> <none>
flux flux-memcached-7c9c56b487-lqsqn 1/1 Running 0 28m 10.10.1.76 aks-default-31698870-0 <none> <none>
kube-system azure-cni-networkmonitor-8gmzl 1/1 Running 0 33m 10.10.1.35 aks-default-31698870-1 <none> <none>
kube-system azure-cni-networkmonitor-qpnpl 1/1 Running 0 33m 10.10.1.4 aks-default-31698870-2 <none> <none>
kube-system azure-cni-networkmonitor-stcgx 1/1 Running 0 33m 10.10.1.66 aks-default-31698870-0 <none> <none>
kube-system azure-ip-masq-agent-8fqdw 1/1 Running 0 33m 10.10.1.66 aks-default-31698870-0 <none> <none>
kube-system azure-ip-masq-agent-jgp2w 1/1 Running 0 33m 10.10.1.4 aks-default-31698870-2 <none> <none>
kube-system azure-ip-masq-agent-mdp75 1/1 Running 0 33m 10.10.1.35 aks-default-31698870-1 <none> <none>
kube-system azure-npm-7hcng 2/2 Running 0 33m 10.10.1.4 aks-default-31698870-2 <none> <none>
kube-system azure-npm-88m7n 2/2 Running 1 33m 10.10.1.35 aks-default-31698870-1 <none> <none>
kube-system azure-npm-bpfzn 2/2 Running 1 33m 10.10.1.66 aks-default-31698870-0 <none> <none>
kube-system coredns-8697646b74-sjxs2 1/1 Running 0 36m 10.10.1.6 aks-default-31698870-2 <none> <none>
kube-system coredns-8697646b74-x8m4h 1/1 Running 0 30m 10.10.1.43 aks-default-31698870-1 <none> <none>
kube-system coredns-autoscaler-567dc76d66-x6dll 1/1 Running 0 36m 10.10.1.19 aks-default-31698870-2 <none> <none>
kube-system kube-proxy-72c7v 1/1 Running 0 33m 10.10.1.35 aks-default-31698870-1 <none> <none>
kube-system kube-proxy-htm5v 1/1 Running 0 33m 10.10.1.66 aks-default-31698870-0 <none> <none>
kube-system kube-proxy-wh9v4 1/1 Running 0 33m 10.10.1.4 aks-default-31698870-2 <none> <none>
kube-system kubernetes-dashboard-9f5bf9974-hzfrz 1/1 Running 3 36m 10.10.1.31 aks-default-31698870-2 <none> <none>
kube-system metrics-server-5695787788-5phvz 1/1 Running 0 36m 10.10.1.29 aks-default-31698870-2 <none> <none>
kube-system tunnelfront-78576c56b8-mzc8c 1/1 Running 0 36m 10.10.1.25 aks-default-31698870-2 <none> <none>
やったね、flux がいますね!
続いて kubectl get services を叩いて、 azure-vote アプリがデプロイされているか確認します。
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
azure-vote-back ClusterIP 10.0.174.148 <none> 6379/TCP 10m
azure-vote-front LoadBalancer 10.0.77.228 52.246.162.40 80:31691/TCP 10m
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 52m
もしうまくいっている場合は azure-vote-back と azure-vote-front が居ます。EXTERNAL-IP (上記では 52.246.162.40) をブラウザで叩くとこんな画面が見れるはずです。
素晴らしい。ついにアプリがデプロイされました。
ハマリポイント 5:
Flux は立ち上がっているけど azure-vote が見つからない場合は Flux でエラーが起きている可能性が高いです。
ログを見ましょう。ポッド名を kubectl get pods -n flux で調べ、kubectl logs でログを見ます。
$ kubectl get pods -n flux
$ kubectl logs flux-96477f6dd-ffcwr -n flux
clone できない場合は鍵周りのエラーが多い気がします。
ts=2020-01-25T04:55:36.282274149Z caller=loop.go:90 component=sync-loop err="git repo not ready: git clone --mirror: fatal: Could not read from remote repository."
3.6 GitOps 体験
アプリがデプロイされただけでは GitOps されているかわかりませんよね?
最後に、azure-vote のマニフェストを更新して、アプリが自動で更新されるかどうかを確認しましょう。
マニフェストがある azure-vote に移動し、azure-vote.yaml を編集します。
$ cd azure-vote
$ vi azure-vote.yaml
ところで azure-vote のアプリのイメージ、どうやって更新しましょう?
何も考えずに azure-vote-front:v2 と検索したら、v2 のイメージがあることを発見しました (さすが Microsoft さん、気が効いていますね!)。なのでこちらを使わせて頂きましょう。
microsoft/azure-vote-front:v1 の部分を microsoft/azure-vote-front:v2 にします。
$ cat azure-vote.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: azure-vote-back
spec:
replicas: 1
selector:
matchLabels:
app: azure-vote-back
template:
metadata:
labels:
app: azure-vote-back
spec:
nodeSelector:
"beta.kubernetes.io/os": linux
containers:
- name: azure-vote-back
image: redis
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 6379
name: redis
---
apiVersion: v1
kind: Service
metadata:
name: azure-vote-back
spec:
ports:
- port: 6379
selector:
app: azure-vote-back
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: azure-vote-front
spec:
replicas: 1
selector:
matchLabels:
app: azure-vote-front
template:
metadata:
labels:
app: azure-vote-front
spec:
nodeSelector:
"beta.kubernetes.io/os": linux
containers:
- name: azure-vote-front
image: microsoft/azure-vote-front:v2 // ★ v1 -> v2 へ
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 80
env:
- name: REDIS
value: "azure-vote-back"
---
apiVersion: v1
kind: Service
metadata:
name: azure-vote-front
spec:
type: LoadBalancer
ports:
- port: 80
selector:
これで準備オーケーです。こちらを commit して push しましょう。
さて、push して数分経ってから Azure DevOps の azure-vote リポジトリの master ブランチの history を見るとflux-sync というタグが、先ほどの v2 の commit に付いていることがわかります。
Flux はこの v2 の commit と sync しています。自動でタグを付けてくれ、Flux が動いていることがわかります。
さてさて、先ほどの IP にブラウザでアクセスしてみましょう。
すると・・
先ほどまでは犬と猫を投票するアプリでしたが、マニフェストを更新しただけで、今度は青か紫かを投票する何とも渋いアプリとなりました。
これで、犬派と猫派で社内で争うことが無くなりそうですね。やりましたね!
まとめ
ここまで読んでくださりありがとうございます。
terraform 初挑戦だった私でも、bedrock を利用して Azure に簡単に GitOps 環境を構築できました。
みなさんもこの機会にぜひ挑戦してみてはいかがでしょうか。
この記事が気に入ったらサポートをしてみませんか?