AKS で git-sync を使って Azure Files に複製するときの試行錯誤
環境
AKS
やりたいこと
- アプリケーションが読み取るファイル(フォルダ階層あり)をボリュームマウントして、永続ストレージとする。その永続ストレージはAzureFilesとファイル共有をする
- アプリケーションが読み取るファイル(フォルダ階層あり)はgitリポジトリにある
- gitリポジトリはそれなりに更新されるので、cloneだけではなくpullもされる必要がある(同期が必要)
- ファイル量/階層はそれなりに多い
- gitのsyncがされたら、アプリケーションpodに通知したい
最初はgitOpsでできるんじゃないかと思ってたけど、なんか思ってたのと違った。
gitOps は ArgoCDを入れている。
Copilotにきいたらこんな感じでできるよ!とは言われたけどできなかった。
Gitリポジトリは 以下。
https://username@dev.azure.com/organizationName/ProjectName/_git/dev-files-sync
-
manifest // ArgoCD のアプリケーションマニフェスト
-
config-files // 実アプリケーションが読み取りたいフォルダ
という構成。
別リポジトリだとややこしいかなと思って、同じリポジトリにしてみたけど、よりややこしくなった感がある。 -
ArgoCDに登録するアプリケーションのマニフェスト
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: files-sync # アプリケーションの名前
spec:
destination:
namespace: default # デプロイ先のネームスペース
server: https://kubernetes.default.svc # デプロイ先のクラスター
project: default # Argo CD のプロジェクト名
source:
path: manifests # Git リポジトリ内のマニフェストファイルがあるパス
repoURL: https://username@dev.azure.com/organizationName/ProjectName/_git/dev-files-sync
targetRevision: HEAD # Git リポジトリのリビジョン
syncPolicy:
automated: # 自動同期を有効にする
prune: false # クラスター内の不要なリソースを削除する
selfHeal: true # クラスター内のリソースの状態を修復する
apiVersion: v1
kind: Pod
metadata:
name: git-repo-sync # ポッドの名前
spec:
containers:
- name: git-repo-sync # コンテナの名前
image: argoproj/gitops-agent # GitOps エンジンを提供するイメージ
args:
- /bin/sh
- -c
- |
git clone https://<PAT>@dev.azure.com/organizationName/ProjectName/_git/dev-files-sync /mnt/sync-files # Git リポジトリを Azure Files にクローンする
cd /mnt/sync-files # クローンしたディレクトリに移動する
gitops --repo-url https://<PAT>@dev.azure.com/organizationName/ProjectName/_git/dev-files-sync --branch main --path . --dest-path /mnt/sync-files # GitOps エンジンを起動する
volumeMounts:
- name: azurefile # ボリュームの名前
mountPath: /mnt/sync-files # マウント先のパス
volumes:
- name: azurefile # ボリュームの名前
persistentVolumeClaim:
claimName: azurefile # 永続ボリュームクレームの名前
AzureFiles を永続ストレージとして使うよ宣言
ストレージアカウントはAKSを作ると自動でできるAKSのネットワークとかもろもろのリソースがあるリソースグループに作成。MC_ から始まるやつ。ここではMC_rg_2 としときます。
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: file.csi.azure.com
name: azurefile
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: azurefile-csi
csi:
driver: file.csi.azure.com
readOnly: false
volumeHandle: unique-volumeid # make sure this volumeid is unique for every identical share in the cluster
volumeAttributes:
resourceGroup: MC_rg_2
shareName: files-sync // ファイル共有名
nodeStageSecretRef:
name: azure-secret
namespace: default
mountOptions:
- dir_mode=0777
- file_mode=0777
- uid=0
- gid=0
- noperm
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: azurefile
spec:
accessModes:
- ReadWriteMany
storageClassName: azurefile-csi
volumeName: azurefile
resources:
requests:
storage: 5Gi
azure-secret は以下のコマンドで登録しておく
kubectl create secret generic azure-secret --from-literal=azurestorageaccountname=<ストレージ名> --from-literal=azurestorageaccountkey=<ストレージキー>
ちなみに、azure-secret をyamlで登録するとなんでか認証が通らなくなる。
apiVersion: v1
kind: Secret
metadata:
name: azure-secret
type: Opaque
data:
azurestorageaccountname: ストレージアカウントをbase64エンコードした値
azurestorageaccountkey: ストレージキー
指定の仕方が悪いんかなぁ
追記
ストレージキーもbase64エンコードが必要だった。
ストレージキー自体がbase64なのになんでやと思ってたけど、コマンドでやるときは値をbase64でエンコードして保存しているけど、ファイルの場合はプレーンで保存されるから、そこで値が一致しなくなるとかなんだろうな。
ということで以下が正解。
apiVersion: v1
kind: Secret
metadata:
name: azure-secret
type: Opaque
data:
azurestorageaccountname: ストレージアカウントをbase64エンコードした値
azurestorageaccountkey: ストレージキーをbase64エンコードした値
ストレージキーのbase64エンコードは以下コマンドで。
base64 -w 0 にしているのは、値を途中で改行されたので。
echo "ストレージキー" | base64 -w 0
さらに追記
yamlファイルにはわざわざエンコードしなくてもいい。関数あるらしい
apiVersion: v1
kind: Secret
metadata:
name: azure-secret
type: Opaque
data:
azurestorageaccountname: {{ .Values.secret.azurestorageaccountname | b64enc }}
azurestorageaccountkey: {{ .Values.secret.azurestorageaccountkey | b64enc }}
azurefiles-pv.yaml
azurefiles-mount-options-pvc.yaml
はHelmチャートに入れ込みたい。
本アプリのpodができる前に作られていてほしい
helmプロジェクトのtemplateフォルダの下に置いておけば良いらしい。
PersistentVolumeやPersistentVolumeClaimはdeploymentより前に動く仕様らしいので、あんまり順序とか気にしなくても最初の方にやってくれる
あとはvalues.yaml に変数切り出したりするだけ。
今現状動いているファイル/フォルダ構成は以下
git-sync-app
├─templates
│ ├─azurefiles-mount-options-pvc.yaml
│ ├─azurefiles-pv.yaml
│ ├─azurefiles-secret.yaml
│ ├─deployment.yaml
│ ├─ingress.yaml
│ └─service.yaml
├─Chart.yaml
└─values.yaml
実アプリケーションのHelmチャート
AzureFilesをマウントしたところまで
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
namespace: {{ default "staging" .Release.Namespace }}
labels:
app: {{ .Values.label.app }}
spec:
replicas: 1
selector:
matchLabels:
app: {{ .Values.label.app }}
template:
metadata:
labels:
app: {{ .Values.label.app }}
spec:
containers:
- name: {{ .Release.Name }}
image: {{ .Values.image.registry }}.azurecr.io/{{ .Values.image.name }}:{{ default "latest" .Values.image.tag }}
resources:
requests:
cpu: 300m
memory: 512Mi
ports:
- containerPort: 8080
volumeMounts:
- name: azurefile
mountPath: /mnt/sync-files
volumes:
- name: azurefile
persistentVolumeClaim:
claimName: azurefile
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
namespace: {{ default "staging" .Release.Namespace }}
spec:
selector:
app: {{ .Values.label.app }}
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
namespace: {{ default "staging" .Release.Namespace }}
annotations:
kubernetes.io/ingress.class: addon-http-application-routing
spec:
rules:
- host: <AKSのサブドメイン>.{{ .Values.dns.name }}
http:
paths:
- backend:
service:
name: {{ .Release.Name }}
port:
name: http
path: /
pathType: Prefix
label:
app: main-app
image:
registry: ACRのリポジトリ名
name: imageの名前
tag: latest
dns:
name: *****.japaneast.aksapp.io
ストレージ周りの値をまだvaluesに持っていけてないから最終的には持っていく
アプリケーションのimageのハッシュが更新されたのに、全然podのイメージが変わらなかった問題
(イメージのタグが変わってないのにハッシュが変わったのは開発であれこれやってるせい)
(運用環境だとタグ変わるから大丈夫かな)
imagePullPolicy を Always で常にレジストリを見に行くようにする
template:
metadata:
labels:
app: {{ .Values.label.app }}
spec:
containers:
- name: {{ .Release.Name }}
image: {{ .Values.image.registry }}.azurecr.io/{{ .Values.image.name }}:{{ default "latest" .Values.image.tag }}
imagePullPolicy: Always
デフォルトはIfNotPresent。
コンテナランタイムにいない場合に取得しに行く、とあるのでローカルキャッシュ的なのを参照するってことかな。
サイドカーコンテナでgit-syncを使うやり方があるっぽいのでやってみる。
試行錯誤した結果できたっぽい。
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
namespace: {{ default "staging" .Release.Namespace }}
labels:
app: {{ .Values.label.app }}
spec:
replicas: 1
selector:
matchLabels:
app: {{ .Values.label.app }}
template:
metadata:
labels:
app: {{ .Values.label.app }}
spec:
containers:
- name: {{ .Release.Name }}
image: {{ .Values.image.registry }}.azurecr.io/{{ .Values.image.name }}:{{ default "latest" .Values.image.tag }}
resources:
requests:
cpu: 300m
memory: 512Mi
ports:
- containerPort: 8080
volumeMounts:
- name: azurefile
mountPath: /mnt/sync-files
- name: git-sync # サイドカーコンテナの名前
image: registry.k8s.io/git-sync/git-sync:v4.1.0 # git-sync
securityContext: # セキュリティコンテキストを追加する
runAsUser: 0 # ユーザーIDを0にする
runAsGroup: 0 # グループIDを0にする
volumeMounts:
- name: azurefile # メインコンテナと同じボリュームをマウントする
mountPath: /mnt/sync-files # マウント先のディレクトリ
args:
- --repo=https://<PAT>@dev.azure.com/organizationName/ProjectName/_git/dev-files-sync
- --depth=1
- --period=30s
- --ref=main
- --link=current
- --root=/mnt/sync-files
volumes:
- name: azurefile
persistentVolumeClaim:
claimName: azurefile # AzureFilesのCSIドライバーを使ってプロビジョニングされたファイル共有を参照するPVCの名前
ずっとpermissionエラーが出て、なかなか解決しなかったけど、AzureFiles にファイルを置くときのユーザが問題だったよう
このsecurityContextがポイント。
ボリュームをマウントするのはrootユーザ(0)
git-syncでcloneとかするときにchmodしたりするらしんだけど、その時のユーザと一致してなくてpermissionで怒られたらしい。
明示的にsecurityContextでユーザを指定してあげるとよいとか。
この辺のことが書いてたページがどっかにいってしまった。。
securityContext: # セキュリティコンテキストを追加する
runAsUser: 0 # ユーザーIDを0にする
runAsGroup: 0 # グループIDを0にする
runAsGroup じゃなくてfsGroupでもよいのかも?
参考
AzureFilesに共有ができたときの中身
current はシンボリックリンク。
--link=current で指定したときの名前で作成される。
指定しない場合は、gitのリポジトリ名で作られた。
で実体はどこにあるかというと、.worktrees の中にブランチ(タグもかな?)ごとにディレクトリが作成される。
このフォルダにリンクが貼られている形なので、使う方(アプリ)側からは、/mnt/sync-files/current を起点にすればよい。
git-sync で使えるオプションは以下
Usage of /git-sync:
--add-user add a record to /etc/passwd for the current UID/GID (needed to use SSH with an arbitrary UID)
--askpass-url string a URL to query for git credentials (username=<value> and password=<value>)
--cookie-file use a git cookiefile (/etc/git-secret/cookie_file) for authentication
--credential credentialSlice one or more credentials (see --man for details) available for authentication (default [])
--depth int create a shallow clone with history truncated to the specified number of commits (default 1)
--error-file string the path (absolute or relative to --root) to an optional file into which errors will be written (defaults to disabled)
--exechook-backoff duration the time to wait before retrying a failed exechook (default 3s)
--exechook-command string an optional command to be run when syncs complete (must be idempotent)
--exechook-timeout duration the timeout for the exechook (default 30s)
--git string the git command to run (subject to PATH search, mostly for testing) (default "git")
--git-config string additional git config options in 'section.var1:val1,"section.sub.var2":"val2"' format
--git-gc string git garbage collection behavior: one of 'auto', 'always', 'aggressive', or 'off' (default "always")
--group-write ensure that all data (repo, worktrees, etc.) is group writable
-h, --help print help text and exit
--http-bind string the bind address (including port) for git-sync's HTTP endpoint
--http-metrics enable metrics on git-sync's HTTP endpoint
--http-pprof enable the pprof debug endpoints on git-sync's HTTP endpoint
--link string the path (absolute or relative to --root) at which to create a symlink to the directory holding the checked-out files (defaults to the leaf dir of --repo)
--man print the full manual and exit
--max-failures int the number of consecutive failures allowed before aborting (-1 will retry forever
--one-time exit after the first sync
--password string the password or personal access token to use for git auth (prefer --password-file or this env var)
--password-file string the file from which the password or personal access token for git auth will be sourced
--period duration how long to wait between syncs, must be >= 10ms; --wait overrides this (default 10s)
--ref string the git revision (branch, tag, or hash) to sync (default "HEAD")
--repo string the git repository to sync (required) (default "https://5h4quzpeia6wn5v273bxxp5xjzqfraltxi6iofeimsjvcy6g7kna@dev.azure.com/mtisoldev/MobileConvert/_git/dev-mcp-site-config")
--root string the root directory for git-sync operations (required) (default "/git")
--sparse-checkout-file string the path to a sparse-checkout file
--ssh-key-file stringArray the SSH key(s) to use (default [/etc/git-secret/ssh])
--ssh-known-hosts enable SSH known_hosts verification (default true)
--ssh-known-hosts-file string the known_hosts file to use (default "/etc/git-secret/known_hosts")
--stale-worktree-timeout duration how long to retain non-current worktrees
--submodules string git submodule behavior: one of 'recursive', 'shallow', or 'off' (default "recursive")
--sync-on-signal string sync on receipt of the specified signal (e.g. SIGHUP)
--sync-timeout duration the total time allowed for one complete sync, must be >= 10ms; --timeout overrides this (default 2m0s)
--touch-file string the path (absolute or relative to --root) to an optional file which will be touched whenever a sync completes (defaults to disabled)
--username string the username to use for git auth
-v, --verbose int logs at this V level and lower will be printed
--version print the version and exit
--webhook-backoff duration the time to wait before retrying a failed webhook (default 3s)
--webhook-method string the HTTP method for the webhook (default "POST")
--webhook-success-status int the HTTP status code indicating a successful webhook (0 disables success checks (default 200)
--webhook-timeout duration the timeout for the webhook (default 1s)
--webhook-url string a URL for optional webhook notifications when syncs complete (must be idempotent)
本家のマニュアルの方が親切かも
本家のマニュアルを見るに、git-syncのv3までは
環境変数は GIT_SYNC_XXXX というプリフィクスだったけど、v4からは
GITSYNC_XXXX という形式になってるっぽい。
よく使うコマンド
- Helm
helm list
helm install リリース名 .
helm upgrade リリース名 .
helm uninstall リリース名
helm history リリース名
- kubectl
# pod のリスト
kubectl get pods
# deploy したpod の詳細を確認
kubectl describe pod リリース名
# アプリケーションがマウントしているボリュームの中の確認
kubectl exec リリース名-deployment-********** -- ls -l /mnt/files-sync
# サイドカー(git-sync)のログ取得
kubectl logs pod名 -c git-sync
# 変更を適用
kubectl apply -f azurefiles-pv.yaml
# シークレットの削除
kubectl delete secret azure-secret
以下のyamlを用意して、適用する。
サービスアカウントの作成
apiVersion: v1
kind: ServiceAccount
metadata:
name: sample-serviceaccount
namespace: app-namespace
$ kubectl apply -f sample-serviceaccount.yaml
$ kubectl get serviceaccount sample-serviceaccount -o yaml -n app-namespace
kubectl を実行できるpodの作成
apiVersion: v1
kind: Pod
metadata:
name: sample-kubectl
namespace: app-namespace
spec:
serviceAccountName: sample-serviceaccount
containers:
- name: kubectl-container
image: lachlanevenson/k8s-kubectl:v1.10.4
command: ["sleep", "86400"]
$ kubectl apply -f sample-kubectl.yaml
$ kubectl get pod sample-kubectl -n app-namespace
ClusterRoleBinding の作成
podのステータスを見たいだけなので、プリセットのviewロールを付与するようにする
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sample-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: sample-serviceaccount
namespace: app-namespace
$ kubectl apply -f sample-clusterrolebinding.yaml
clusterrolebinding.rbac.authorization.k8s.io/sample-clusterrolebinding created
$ kubectl describe clusterrolebinding sample-clusterrolebinding
Name: sample-clusterrolebinding
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: view
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount sample-serviceaccount app-namespace
確認
$ kubectl exec -it sample-kubectl -n app-namespace-- kubectl get pods
NAME READY STATUS RESTARTS AGE
git-sync 1/1 Running 0 3h
sample-kubectl 1/1 Running 0 2h
本当にやりたかったコマンド
$ kubectl exec -it sample-kubectl -n app-namespace -- /bin/sh -c "until [ \"$(kubectl get pods -l app=git-sync -n app-namespace -o jsonpath='{.items[0].status.phase}')\" == 'Running' ]; do sleep 1; done"