Tanzu Kubernetes GridでHarborをオレオレ証明書で構築後、このHarborの証明書をノードに入れないとイメージのpullが出来ない。
例えばnginxを使って起動しようとすると、以下のようなエラーになる。
Normal Pulling 7s (x2 over 19s) kubelet Pulling image "harbor.10-220-14-115.sslip.io/library/nginx"
Warning Failed 7s (x2 over 19s) kubelet Failed to pull image "harbor.10-220-14-115.sslip.io/library/nginx": rpc error: code = Unknown desc = failed to pull and unpack image "harbor.10-220-14-115.sslip.io/library/nginx:latest": failed to resolve reference "harbor.10-220-14-115.sslip.io/library/nginx:latest": failed to do request: Head "https://harbor.10-220-14-115.sslip.io/v2/library/nginx/manifests/latest": x509: certificate signed by unknown authority
これを回避するために、ノードにHarborの証明書を埋め込むのだが、TKG2.1ではplan-basedとclass-basedという2パターンのクラスタタイプが用意されており、それぞれで証明書のインストール方法が異なる。またアップデート後の挙動も異なるため、それを検証した時のメモとなる。
前準備
テスト用のイメージをPrivate Harborに入れておく。docker loginするためにPrivate HarborのSecretのharbor-tls
をdocker daemonに読み込ませる。
export HARBOR_DOMAIN="harbor.10-151-200-240.sslip.io"
sudo mkdir -p /etc/docker/certs.d/$HARBOR_DOMAIN
kubectl get secret -n tanzu-system-registry -o yaml harbor-tls -o jsonpath="{.data.ca\.crt}" | base64 -d | sudo tee /etc/docker/certs.d/$HARBOR_DOMAIN/ca.crt
sudo systemctl restart docker
Private Harborにdocker loginし、テストイメージとしてnginxをpushする。
export HARBOR_ID=admin
export HARBOR_PW=xxxx
echo $HARBOR_PW | docker login $HARBOR_DOMAIN -u $HARBOR_ID --password-stdin
docker pull nginx
docker tag nginx ${HARBOR_DOMAIN}/library/nginx
docker push ${HARBOR_DOMAIN}/library/nginx
新規・既存クラスタへの証明書の埋め込み
既存クラスタへの埋め込み
Private Harborをデプロイしたクラスタ自身からHarborを参照したい場合など、既存のクラスタでPrivate Harborを利用したい場合などはこちらの手順を利用する。
オフィシャルドキュメントの手順はこちらになる。
なお、ドキュメントだと何故かKubeadmControlPlane
リソースへの埋め込みがスキップされている。KubeadmControlPlane
に入れないとControlPlaneに反映されない。
ただ、ControlPlaneでPrivate Harborからpullするケースは今回想定していないため、ドキュメントに合わせてWorkerノードにのみ証明書を導入する。
最初にHarborの証明書を取得しデコードしたものを控えておく。
kubectl get secret -n tanzu-system-registry -o yaml harbor-tls -o jsonpath="{.data.ca\.crt}" | base64 -d
Management Clusterにコンテキストを切り替える。
kubectl config use-context tkg21mc
最初にWorkerノードを管理するkubeadmconfigtemplate
リソースを書き換える。
オブジェクト名はClusterClass
リソースの名前が付いているものを指定する。
kubectl edit kubeadmconfigtemplate tkg-vsphere-default-v1.0.0-md-config
kubeadmconfigtemplate
リソースはspec.template.spec.files
に定義したファイルをノード作成時に生成してくれる。
既存のfiles
に追記する形で以下を追記する。
- content: |
-----BEGIN CERTIFICATE-----
MIIDKDCCAhCgAwIBAgIQHmZgcfI/GYpB0qIz0BwmRTANBgkqhkiG9w0BAQsFADAU
:(省略)
XcSmQr62lueUki8kqLcIDGnI2p3j+YmzO+fZxKjmIn+XySPy8TGcOwz9tjs=
-----END CERTIFICATE-----
owner: root:root
path: /etc/ssl/certs/tkg-custom-ca.pem
permissions: "0644"
また、spec.template.spec.preKubeadmCommands
に以下を追加する。
- '! which rehash_ca_certificates.sh 2>/dev/null || rehash_ca_certificates.sh'
- '! which update-ca-certificates 2>/dev/null || (mv /etc/ssl/certs/tkg-custom-ca.pem
/usr/local/share/ca-certificates/tkg-custom-ca.crt && update-ca-certificates)'
ノードデプロイ時、中でkubadmによるノードのクラスタ追加が実施されるが、preKubeadmCommands
はその前に実行するコマンドを指定するオプションであり、ここでは証明書のインストールコマンドを叩いている。。 PhotonOSとUbuntuでパスが異なるため、両方対応出来るよう2種類のインストールコマンドを記載しており、ubuntuではupdate-ca-certificates
を実行して上記で作成した証明書を読み込ませている。
元々ある設定値を含めると、preKubeadmCommands
は以下のようになった。
preKubeadmCommands:
- hostname "{{ ds.meta_data.hostname }}"
- echo "::1 ipv6-localhost ipv6-loopback" >/etc/hosts
- echo "127.0.0.1 localhost" >>/etc/hosts
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >>/etc/hosts
- echo "{{ ds.meta_data.hostname }}" >/etc/hostname
- '! which rehash_ca_certificates.sh 2>/dev/null || rehash_ca_certificates.sh'
- '! which update-ca-certificates 2>/dev/null || (mv /etc/ssl/certs/tkg-custom-ca.pem
/usr/local/share/ca-certificates/tkg-custom-ca.crt && update-ca-certificates)'
次にWorkerノードを再作成する。MachineDeployment
リソースはannotationに変更を加えるとWorkerノードを再作成する。annotationに以下のpatchコマンドで変更を加えてノードに証明書が入った状態にする。
kubectl patch machinedeployments tkg21wc-md-0-8jpw5 --type merge -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"date\":\"`date +'%s'`\"}}}}}"
しばらくするとWorkerノードのみ再作成される。
再作成された後、動作確認する。
pullする際の認証情報をSecretとして作成し、defaultのServiceAccountに紐付ける。ここではkubectl patch
で紐付けているが、既存のimagePullSecret設定があるならkubectl edit
で代用すること。
kubectl create secret docker-registry --docker-server=$HARBOR_DOMAIN --docker-username=$HARBOR_ID --docker-password=$HARBOR_PW myharbor-cred
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myharbor-cred"}]}'
テスト用のイメージを起動する。
kubectl run --image ${HARBOR_DOMAIN}/library/nginx nginx
無事起動すればOK。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 43s
class-basedの新規クラスタへの埋め込み
実は特に作業はない。
従来だと、既存のクラスタへのカスタム CA 証明書の信頼の追加(スタンドアローン MC)の手順にて追加するが、後述の余談の節に記載しているようにclass-basedだと自動でtkg-vsphere-default-v1.0.0-md-config
をベースとしたkubeadmconfigtemplates
が作成されるので対応不要。
試しに新規にクラスタを作成する。
tanzu cluster create -f tkg21wc-2.yaml -v9
構築後、コンテキストを切り換えてPrivate Harborのnginxを起動する。
tanzu cluster kubeconfig get --admin tkg21wc-2
kubectl config use-context tkg21wc-2-admin@tkg21wc-2
kubectl run --image $HARBOR_DOMAIN/library/nginx nginx
無事起動している。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 31s
plan-basedの新規クラスタへの埋め込み
plan-basedの場合はkubeadmconfigtemplates
の元となるものなく新規に作成される。新規に作成されるkubeadmconfigtemplates
をカスタマイズしたい場合、リソースに修正をかけるytt overlayを~/.config/tanzu/tkg/providers/ytt/03_customizations
以下に設置しておく必要がある。
公式ドキュメントをベースにytt overlayを作成する。
最初にコンテキストをWorkload Clusterに切り替えておく。
kubectl config use-context tkg21wc
ytt overlayファイルを作成する。
export YTT_DIR=~/.config/tanzu/tkg/providers/ytt/03_customizations
kubectl get secret -n tanzu-system-registry -o yaml harbor-tls -o jsonpath="{.data.ca\.crt}" | base64 -d > ${YTT_DIR}/tkg-custom-ca.pem
cat << 'EOF' > ${YTT_DIR}/add-custom-ca.yaml
#@ load("@ytt:overlay", "overlay")
#@ load("@ytt:data", "data")
#@overlay/match by=overlay.subset({"kind":"KubeadmConfigTemplate"}), expects="1+"
---
spec:
template:
spec:
#@overlay/match missing_ok=True
files:
#@overlay/append
- content: #@ data.read("tkg-custom-ca.pem")
owner: root:root
permissions: "0644"
path: /etc/ssl/certs/tkg-custom-ca.pem
#@overlay/match missing_ok=True
preKubeadmCommands:
#! For Photon OS
#@overlay/append
- '! which rehash_ca_certificates.sh 2>/dev/null || rehash_ca_certificates.sh'
#! For Ubuntu
#@overlay/append
- '! which update-ca-certificates 2>/dev/null || (mv /etc/ssl/certs/tkg-custom-ca.pem /usr/local/share/ca-certificates/tkg-custom-ca.crt && update-ca-certificates)'
EOF
plan-basedの設定ファイルを利用してWorkloadクラスタを作成する
tanzu config set features.cluster.allow-legacy-cluster true
tanzu cluster create -f tkg21wc-addcert-planbased.yaml -v9
コンテキストを切り替えてPrivate Harborのイメージを起動する。
tanzu cluster kubeconfig get --admin tkg21wc-addcert
kubectl config use-context tkg21wc-addcert-admin@tkg21wc-addcert
kubectl run --image $HARBOR_DOMAIN/library/nginx nginx
こちらでも問題なくpullできることが分かる。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 61s
TKGのバージョンアップ後の証明書
今回、バージョンアップの検証もしたかったので、TKG2.1.0でManagement Clusterを構築した。これを2.1.1にバージョンアップして、証明書が再度取り込まれるかを確認する。
Management ClusterをTKG2.1.1にアップグレードする。公式ドキュメントはこちら。
tanzu cliをv0.28.0からv0.28.1に変更する。
$ tanzu version
version: v0.28.1
buildDate: 2023-03-07
sha: 0e6704777-dirty
v0.28.1用のプラグインをインストールする。
tanzu init
Kubernetesバージョンv1.24.10+vmware.1のOVAを転送してテンプレート化し、upgradeを実行する。
tanzu mc upgrade
アップデート後、ClusterClassに紐付いたkubeadmconfigtemplates
リソースのtkg-vsphere-default-v1.0.0-md-config
が変更されたままかを確認する。
$ kubectl get kubeadmconfigtemplates -o yaml tkg-vsphere-default-v1.0.0-md-config
:(省略)
preKubeadmCommands:
- hostname "{{ ds.meta_data.hostname }}"
- echo "::1 ipv6-localhost ipv6-loopback" >/etc/hosts
- echo "127.0.0.1 localhost" >>/etc/hosts
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >>/etc/hosts
- echo "{{ ds.meta_data.hostname }}" >/etc/hostname
useExperimentalRetryJoin: true
:(省略)
files
に書いた証明書もpreKubeadmCommands
に書いた証明書のインストールコマンドも消えている。
これにより、class-basedだとTKGのバージョンアップ後は追記した証明書情報はリセットされることが分かる。
次に、class-basedとplan-basedのクラスタをそれぞれupgradeしてみる。
tanzu cluster upgrade tkg21wc
tanzu cluster upgrade tkg21wc-addcert
それぞれkubeadmconfigtemplates
リソースは作成され直すが、plan-basedで作成していたクラスタはTKG2.1.0と同じytt overlayを利用してデプロイされるため、証明書や証明書のインストールコマンドは入っている。
preKubeadmCommands:
- hostname "{{ ds.meta_data.hostname }}"
- echo "::1 ipv6-localhost ipv6-loopback" >/etc/hosts
- echo "127.0.0.1 localhost" >>/etc/hosts
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >>/etc/hosts
- echo "{{ ds.meta_data.hostname }}" >/etc/hostname
- '! which rehash_ca_certificates.sh 2>/dev/null || rehash_ca_certificates.sh'
- '! which update-ca-certificates 2>/dev/null || (mv /etc/ssl/certs/tkg-custom-ca.pem
/usr/local/share/ca-certificates/tkg-custom-ca.crt && update-ca-certificates)'
useExperimentalRetryJoin: true
一方でplan-basedで作成していたクラスタは更新後のtkg-vsphere-default-v1.0.0-md-config
を元にしたkubeadmconfigtemplates
で再作成されるため、証明書や証明書のインストールコマンドは消える。
preKubeadmCommands:
- hostname "{{ ds.meta_data.hostname }}"
- echo "::1 ipv6-localhost ipv6-loopback" >/etc/hosts
- echo "127.0.0.1 localhost" >>/etc/hosts
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >>/etc/hosts
- echo "{{ ds.meta_data.hostname }}" >/etc/hostname
useExperimentalRetryJoin: true
更新後の環境でサンプルのnginxを起動した結果はこちら。
■plan-based
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 4s
■class-based
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 0/1 ErrImagePull 0 2s
結論
class-basedでインストールした環境だと、Private Harborの証明書の適用はクラスタ内のリソースを更新するだけでよく、ytt overlayのファイルを用意せずに他のクラスタに伝搬できるため、その点は便利である。
一方で、TKGのバージョンアップを行うと、設定が消えてしまう。
現時点ではManagement Clusterの更新のタイミングで設定ファイルを読ませる事も出来ないため、アップグレード後に手動で設定し直す必要が現時点ではありそうだ。
一方、plan-basedだとアップグレード後の修正コストが発生しないが、ytt overlayファイルを用意する必要があったり、デプロイ後の環境に対して適用する場合は個々にkubectl edit
を実施する煩雑さが出てくる
class-basedがデフォルトとなり、plan-basedが廃れていきそうな感じはあるが、現時点ではユースケースに合わせて使い分けるのが良さそう。
余談:class-basedとplan-basedのkubeadmconfigtemplates
TKG2.1でクラスタ構築時に使う定義ファイルの書き方がclass-basedが新たに追加され、そちらがデフォルトとなり、従来の定義ファイルはplan-basedというタイプに分類され、レガシー扱いされるようになった。
これ、書き方が変わっただけのように見えなくもないが、実際は動作も異なっている。
以下、class-basedとplan-basedでクラスタを作成した状態でkubeadmconfigtemplates
を取得した結果である。
$ kubectl get kubeadmconfigtemplates
NAME AGE
tkg-vsphere-default-v1.0.0-md-config 2d16h ★ClusterClassと紐付き
tkg21wc-addcert-md-0 8m22s ★plan-based
tkg21wc-md-0-bootstrap-ckrrb 9h ★class-based
class-basedの方は語尾にランダムな文字が付与されている。これはclass-basedだとkubeadmconfigtemplates
が都度自動生成されることを意味する。
MachineDeployment
リソースを修正すると、class-basedで作成したクラスタの場合、class-basedのkubeadmconfigtemplates
の種であるtkg-vsphere-default-v1.0.0-md-config
をベースに新規にkubeadmconfigtemplates
リソースが作成される。
そのため、従来型に慣れてる人でkubeadmconfigtemplates
を修正していた人は手順が変わるため注意が必要である。