ピリカでは撮影したごみの画像に対してアノテーションし、その結果を基に解析・研究を進めることがあります。
その中でGCP上の自社サービスと連携できるアノテーションツールが必要になり、CVATをGKEに構築しました。
前提事項
- Kubernetesに関し、一定の基礎知識がある前提で記載しています。
- 開発環境はmacOSで検証しています。
CVATとは?
CVAT(Computer Vision Annotation Tool)はOpenCV teamが提供している、画像アノテーションツールです。以下の機能があります。
- Kubernetes上で、システムをマイクロサービスとして稼働できる
- セルフホスティングして稼働できる
- API, SDKが提供されており、APIを介してデータの操作が行える
- このため、既存システムと連携して動かすことができる
- 事前学習モデルを基に、自動でアノテーションできる
同様のツールでlabelimg, annofabがあるものの、GCP上の他システムとの連携のしやすさからCVATを採用しました1。
構築したいシステム構成
下図の様に、GCP上に構築している他システムと連携できるように構築することとしました。
ポイントは以下の点です。
- 保守のしやすさから、SSL証明書はマネージドのものを使います。
- L7ロードバランサを作るので$0.6/日の費用がかかるものの、保守に掛かる手間を勘案して許容しています。
- HTTPからのアクセスはロードバランサで弾き、HTTPSのみ許容します。また、ドメインへのアクセスのみ許容します。
環境準備
Cloud SDKのインストールや、GCPのプロジェクト構築については割愛します。
本書の作成時点で、以下の構成で動作確認しています。
- Google Cloud SDK 409.0.0
- gke-gcloud-auth-plugin 0.4.0
- core 2022.11.04
- Kubernetes v1.25.2-alpha+ae91c1fc0c443c464a4c878ffa2a4544483c6d1f
-
CVAT v2.3.0 + PR#5505 + PR#5702
- ※ 各PRが導入されたv2.4.2以上を利用することをおすすめします。
- Helm v3.10.2
また、本手順は公式マニュアルの下記手順をベースとしています。
(未実施であれば) GKE APIの有効化
以下のURLより、GKE APIを有効にします(GCPプロジェクトIDの箇所は適宜修正してください)。
https://console.cloud.google.com/flows/enableapi?apiid=container.googleapis.com&hl=ja&project=(お使いのGCPプロジェクトID)
(未実施であれば) 必要ツールのインストール
helmのインストール
kubernetesまわりのパッケージマネージャのhelmがCVAT側で使われているので、インストールします。
$ brew install helm
$ helm version
version.BuildInfo{Version:"v3.10.2", GitCommit:"50f003e5ee8704ec937a756c646870227d7c8b58", GitTreeState:"clean", GoVersion:"go1.19.3"}
kubectlのインストール
Kubernetesのクラスターの操作に必要です。本ツールにより、pod, svc, ingress等のリソースを管理します。
$ brew install kubectl
$ kubectl version --client
version.Info{Major:"1", Minor:"25", GitVersion:"v1.25.4",...(後略)}
Kubectl authentication pluginを導入する
GKE v1.26以降、Kubectl authentication pluginが認証ツールとして推奨されています。以下コマンドでインストールします。
$ gcloud components install gke-gcloud-auth-plugin
$ gke-gcloud-auth-plugin --version
Kubernetes v1.25.2-alpha+ae91c1fc0c443c464a4c878ffa2a4544483c6d1f
合わせて、環境変数にて同ツールが有効になるように.zshrcに書いておきましょう。
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
GKEへの構築方法
クラスター作成
以下コマンドにより、クラスターを作成します(APIについては公式リファレンス参照)。5~10分くらい掛かるので待機します。
CVAT v2.4.0現在において、GKE Autopilot上では安定稼働しないためGKE Standardでクラスターを作成します。
※ ノード数が1以上あると、ノード稼働でCPUおよびメモリ使用量が常時発生します。もし課金を止めたい場合、ノード数を0にしてクラスターを停止させておくと良いです(ただし、引き続きストレージ容量は発生します)。
# machine-typeについて、改行直前にスペースがあるとバリデーションエラーになるので注意
$ gcloud container clusters --project your-project-id\
create cvat-cluster\
--zone asia-northeast1-c\
--node-locations asia-northeast1-c\
--num-nodes 1\
--max-nodes 1\
--min-nodes 0\
--disk-size 25\
--machine-type e2-custom-2-4096\
--enable-network-policy\
--service-account=cvat@your-project-id.iam.gserviceaccount.com
上記では、以下の設定でクラスタを生成します。
- ゾーン、ノードの場所: 東京リージョン(c)
- ノード数: 1 (最大1, 最小0)
- ブートディスクの永続ディスク容量: 25GB
- ディスク容量は後からシュリンクできないため、小さめに定義しています。
- マシンタイプ: e2インスタンス(2 vCPU, 4GB)
- e1-standardでvCPU=1だとCPU不足で起動できません。
- e2-standard-1だとメモリ不足になり、e2-standard-2だとメモリが8GB余るためメモリ使用量をカスタムしています。
無事に作成できていれば、以下のコマンドで作成したクラスターを確認できます。
$ gcloud container clusters --project your-project-id list
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
cvat-cluster asia-northeast1-c 1.24.10-gke.2300 xx.xxx.xxx.xxx e2-custom-2-4096 1.24.10-gke.2300 1 RUNNING
クラスターへのアクセス認証
以下のコマンドにより、作成したクラスターへの認証情報を取得できます。
$ gcloud container --project your-project-id \
clusters get-credentials cvat-cluster \
--zone asia-northeast1-c
認証情報が取れていれば、kubectl get pods -A
コマンドにより今稼働している全名前空間のpod一覧を確認できます。
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-node-k44vh 1/1 Running 0 8m58s
kube-system calico-node-vertical-autoscaler-7fd97448b6-jv97h 1/1 Running 0 10m
kube-system calico-typha-5d4dc54774-xb4gz 1/1 Running 0 9m3s
...
ネットワーク関連の設定
GoogleマネージドSSL証明書の準備
traefikの代わりにGKE Ingressを利用します。
- 以下の通り、cert.yamlファイルを生成します。metadata.nameが証明書の名前になります。
cert.yaml
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: cvat-managed-cert
spec:
domains:
- cvat.your.domain
- 以下コマンドにより、GoogleマネージドSSL証明書を作成します。
$ kubectl apply -f cert.yaml
作成後、以下のコマンドで作成できたことを確認します。
$ kubectl describe managedcertificate cvat-managed-cert
Spec:
Domains:
cvat.your.domain
Status:
Certificate Name: mcrt-***
Certificate Status: Provisioning
Domain Status:
Domain: cvat.your.domain
Status: Active
Expire Time: ***
...
CVATデプロイ後、正常に稼働していれば上記Certificicate StatusがActiveになります。
※ 当該ドメインおよび静的アドレスについて、DNSツール上でAレコードで紐付け設定してください。
グローバルな静的IPアドレスを予約する
注意: 予約した状態で使用されていない場合、静的IPアドレスの予約費用がかかります。そのため、この作業が終わり次第、後述のデプロイまで終わらせてください。
- グローバルな静的IPアドレスを作ります。
$ gcloud compute addresses create your-cvat-static-ip --global --project your-project-id
- 作成できたか確認します(STATUS = RESERVEDになっているはずです)
$ gcloud compute addresses list --global --project your-project-id
NAME ADDRESS/RANGE TYPE PURPOSE NETWORK REGION SUBNET STATUS
your-cvat-static-ip ***.***.***.*** EXTERNAL RESERVED
CVATのデプロイ用テンプレート設定値の準備
cvatのv2.3.0をチェックアウトします。
その後、以下のファイルを編集します。
Charts.yaml
- condition: ingress.enabled
+ condition: traefik.enabled # GCE Ingressを利用できるよう、values側で指定があればtraefikが立ち上がるようにします。
values.override.yaml
以下の設定を行います。
- backend, frontendサービスをNodePortタイプにします。
- GKE Ingressから両サービスのIPが特定するためです。
- postgresqlの保守用パスワードを記載します(適宜決定ください)。
- Ingressについて、ルーティング設定およびHTTPSや静的アドレスの設定を入れます。
- kubernetes.io/ingress.global-static-ip-name: 予約した静的IPアドレス名を指定します。
- networking.gke.io/managed-certificates: 予約したマネージド証明書名を指定します。
- kubernetes.io/ingress.allow-http: HTTPプロトコルでのアクセスを禁止します。
- kubernetes.io/ingress.class: GKE Ingressを使います。
- hostにて、特定ドメインからのみアクセスを許可します。
- traefikをオフにします。
- cvat-traefikサービスが作られず、これにともないL4LBが生成されません。代わりに、GCE IngressによりL7LBが作成され、上述のマネージド証明書が割り当てられます。
cvat:
backend:
server:
envs:
USE_ALLAUTH_SOCIAL_ACCOUNTS: "false" # SSOログインはv2.3.0では利用できない
service:
spec:
type: NodePort
frontend:
service:
type: NodePort
postgresql:
secret:
password: 利用パスワード
postgres_password: superuserの生パスワード
replication_password: レプリケーション用パスワード
ingress:
annotations:
kubernetes.io/ingress.global-static-ip-name: "your-cvat-static-ip" # 予約した静的IPアドレス名を指定する
networking.gke.io/managed-certificates: "cvat-managed-cert" # 予約したマネージド証明書名を指定する
kubernetes.io/ingress.allow-http: "false" # HTTPSでの接続のみ許可する
kubernetes.io/ingress.class: gce # GCE Ingressを使うよう設定する
hosts:
- host: "cvat.your.domain"
paths: # 以下、values.yamlからの変更はなし
- path: /api
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /admin
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /static
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /django-rq
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /git
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /opencv
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path: /profiler
pathType: "Prefix"
service:
name: backend-service
port: 8080
- path : /
pathType: "Prefix"
service:
name: frontend-service
port: 80
traefik:
enabled: false # GCE Ingressを使うため、traefikを無効化する
templates/cvat_backend/service.yml
cvat_backendのLBからのヘルスチェックパスを修正します。
annotations:
+ cloud.google.com/backend-config: '{"default":"{{ .Release.Name }}-backend-server-healthcheck"}'
{{- with .Values.cvat.backend.service.annotations }}
templates/cvat_backend/backend_config.yml
同上です。metadata.nameはservice.ymlのcloud.google.com/backend-config
に渡している値と合わせます。
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: {{ .Release.Name }}-backend-server-healthcheck
spec:
healthCheck:
requestPath: /api/server/health/?format=json
デプロイ
コンテキストを確認します。
$ kubectl config current-context
実行例
以下のように、gke_(GCPプロジェクト名)_(ゾーン名)_(クラスタ名)
という形でcontextが表示されます。
gke_your-project-id_asia-northeast1-c_cvat-cluster
依存関係のインストールを行います(helm-chartディレクトリで実施してください)。
$ helm dependency update
以下コマンドにより、デプロイを行います(CVATのルートディレクトリで実施してください)。
$ helm upgrade -n default cvat \
-i --create-namespace ./helm-chart \
-f ./helm-chart/values.yaml \
-f ./helm-chart/values.override.yaml
実行後、以下の様な表示ができていればデプロイ完了です!
NAME: cvat
LAST DEPLOYED: Thu Dec 1 15:48:39 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
事後確認
pod
以下の様に、cvat-backend, cvat-frontend, cvat-opa, cvat-postgresql, cvat-redis-*ごとのpodsが立ち上がります。すべてSTATUSがRUNNINGであればOKです。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
cvat-backend-server-xxx-xxx 1/1 Running 0 2d7h
cvat-backend-utils-xxx-xxx 1/1 Running 0 2d7h
cvat-backend-worker-default-xxx-xxx 1/1 Running 0 2d7h
cvat-backend-worker-default-xxx-xxx 1/1 Running 0 2d7h
cvat-backend-worker-low-xxx-xxx 1/1 Running 0 2d7h
cvat-backend-worker-webhooks-xxx-xxx 1/1 Running 0 2d7h
cvat-frontend-xxx-xxx 1/1 Running 0 2d7h
cvat-opa-xxx-xxx 1/1 Running 0 2d7h
cvat-postgresql-0 1/1 Running 0 2d7h
cvat-redis-master-0 1/1 Running 0 2d7h
cvat-redis-replicas-0 1/1 Running 0 2d7h
cvat-redis-replicas-1 1/1 Running 0 2d7h
cvat-redis-replicas-2 1/1 Running 0 2d7h
ingress
以下の様に、HOSTSにvalues.override.yamlで設定したドメイン、ADDRESSに確保した静的IPアドレスが入っていればOKです。
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
cvat <none> cvat.your.domain xx.xxx.xxx.xxx 80 13d
静的IPアドレス
デプロイ後、数分経ってから確認します。IngressとIPが紐付いていれば、IN_USE
になっています。
$ gcloud compute addresses list --global
NAME ADDRESS/RANGE TYPE PURPOSE NETWORK REGION SUBNET STATUS
your-cvat-static-ip ***.***.***.*** EXTERNAL IN_USE
証明書
デプロイしてから、最長24時間待つ必要があります。ドメインからCVATにアクセスできていれば、証明書およびドメインともにActiveになります。
ページ表示
HTTPSで設定したドメインにアクセスして、以下の画面が出ていればOKです。
なお、CVATをデプロイした直後は起動中のため、502エラーが発生することがあります。
Q&A
CVATのデータをバックアップしたい
以下ページにあるとおり、CVATのデータはpostgresqlにある分とバックエンド上にある分の2つがあります。これら2つのバックアップを取っていれば、後で復元することができます。
GKEの場合、以下の様にすることでpodから永続データのバックアップができます。
$ kubectl cp cvat-backend-server-xxx-xxx:/home/django/data ~/your/backup/dir/server/data
$ kubectl cp cvat-postgresql-0:/bitnami/postgresql/data ~/your/backup/dir/postgresql/data --retries 10 # データ量が数百MBある場合があり失敗する可能性があるため、通信リトライを入れている
-
labelimgの後方互換ツールでLabel Studioが開発されており、そちらもCVATとほぼ同等の機能が利用可能です。こちらはCloud Runを使ってホスティングすることができます。 ↩