テックブログ_ヘッダー画像__8_

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 の方は、下記ページから環境に合わせてインストールしましょう。

Azure CLI

terraform

helm

ハマリポイント 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 クレジットも提供されます。

参考: Azure 無料アカウント FAQ


2.3 Azure DevOps にリポジトリ作成

マニフェストを置くリポジトリを作成します。

今回は Azure DevOps を使用していきます。もちろん GitHub でもできると思います。

画像2

組織名を聞かれます。任意の名前で大丈夫です。

ここでは pocke-tech-blog としました。

リージョンは East Asia のままで良いと思います。

画像3

続いて、プロジェクト名を聞かれます。

ここでは pocke-tech-blog-project としました。

公開設定は Private を選択しておきましょう。

画像3

プロジェクトができたらリポジトリを作ります。

1. 左下の「Project settings」をクリック。

画像4

2. 左メニューの「Repositories」をクリック。

3. 真ん中の「New repository」をクリック。

画像5

4. Repository name を入力。ここでは azure-vote としました。

5. 「Create」をクリック。

画像6

これで完了です。

ちなみに下記のように 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」をクリックします。

画像7

「Add」をクリックします。

画像8

4. Description に任意の名前を入力 (ファイル名と合わせる必要無いです。)

5. Key Data に先ほどコピーした公開鍵をペースト。

6. 「Save」をクリック。

画像9

参考: Use SSH key authentication


2.5 Azure DevOps のリポジトリを clone

URL まで辿り着くのが初見だと難しかったので、この章もキャプチャ多めです (画面すぐ変わっちゃいますが・・。)

1. 左メニューで「Repos」をクリック。

画像10

2. 上メニューのプルダウンをクリック。

3. 対象のリポジトリを選択。

4.「Clone」をクリック。

画像12

5. SSH を選択。

6. コピー。

画像12

これでやっと 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) をブラウザで叩くとこんな画面が見れるはずです。

画像13

素晴らしい。ついにアプリがデプロイされました。

ハマリポイント 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 が動いていることがわかります。

画像14

さてさて、先ほどの IP にブラウザでアクセスしてみましょう。

すると・・

画像15

先ほどまでは犬と猫を投票するアプリでしたが、マニフェストを更新しただけで、今度は青か紫かを投票する何とも渋いアプリとなりました。

これで、犬派と猫派で社内で争うことが無くなりそうですね。やりましたね!

まとめ

ここまで読んでくださりありがとうございます。
terraform 初挑戦だった私でも、bedrock を利用して Azure に簡単に GitOps 環境を構築できました。
みなさんもこの機会にぜひ挑戦してみてはいかがでしょうか。



この記事が気に入ったらサポートをしてみませんか?