OKD の Image Registry を有効化する

ベアメタル環境など、共有可能なオブジェクトストレージを提供しないプラットフォーム上に OpenShift/OKD クラスタを構築した場合、デフォルトでは Image Registry が無効になっている。本記事では、OKD4 で Image Registry を有効化する手順を説明する。

## 前提条件
- Container CLI: `docker`, `podman`, `nerdctl`, etc.
- OKD4 cluster
  - OpenShift Data Foundation
## 手順
手順は次の公式ドキュメントに従う：

https://docs.openshift.com/container-platform/4.15/registry/configuring_registry_storage/configuring-registry-storage-rhodf.html

### Image Registry の有効化
Image Registry Operator が稼働していることを確認する：
```
$ oc get clusteroperator image-registry
NAME                                       VERSION                          AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry                             4.15.0-0.okd-2024-03-10-010116   True        False         False      16h     
```

次に、`spec.managementState` を `Removed` から `Managed` に編集する。
次のコマンドを実行し、Image Registry を有効化する：
```
oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch '{"spec":{"managementState":"Managed"}}'
```

ここで再度 Image Registry Operator を検索する：
```
$ oc get clusteroperator image-registry
NAME                                       VERSION                          AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry                             4.15.0-0.okd-2024-03-10-010116   False       False         True       14m     Available: Error: storage backend not configured...
```

今度は `Available` カラムが `False` になり、`MESSAGE` カラムが `Available: Error: storage backend not configured...` が表示されている。
これは、Image Registry は有効化されたものの、まだストレージバックエンドがまだ用意されておらず、エラーを吐いている状態である。

## ストレージバックエンドの作成
各種クラウドサービスで構築した S3 bucket や、ベアメタル環境で構築した PV などを指定することができる。
（公式ドキュメンテーションにさまざまなプラットフォームにおける設定例があるので、詳しくはそちらを参照：[Image Registry Operator in OKD | Registry | OKD 4.15](https://docs.okd.io/4.15/registry/configuring-registry-operator.html))

本節では、OpenShift 上のストレージ統合基盤 ODF 上に S3 bucket を作成し、それを Image Registry のストレージバックエンドに指定する方法を説明する。

ODF の導入が済んでいない場合は次の Qiita 記事も参照：

https://qiita.com/sawa2d2/items/f8c2b37340b59b4ee579

### namespace の切り替え
Image Regsitry 用の project `openshift-image-registry` に切り替える：
```
$ oc project openshift-image-registry
Now using project "openshift-image-registry" on server "https://api.ocp4.example.com:6443".

# 確認
$ oc project openshift-image-registry
Already on project "openshift-image-registry" on server "https://api.sno.example.com:6443".
```

### S3 bucket の作成
次の Object Bucket Claim を発行し、`openshift-storage` namespace 内に S3 bucket を作成する：

```
$ cat <<EOF | oc apply -f -
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: noobaatest
  namespace: openshift-storage 
spec:
  storageClassName: openshift-storage.noobaa.io
  generateBucketName: noobaatest
EOF
```

bucket が作成されたことを確認する：
```
$ oc get objectbucket
NAME                               STORAGE-CLASS                 CLAIM-NAMESPACE   CLAIM-NAME   RECLAIM-POLICY   PHASE   AGE
obc-openshift-storage-noobaatest   openshift-storage.noobaa.io                                  Delete           Bound   11s
```

bucket 名を取得する：
```
bucket_name=$(oc get obc -n openshift-storage noobaatest -o jsonpath='{.spec.bucketName}')
```

bucket の AWS credential keys を取得する：
```
AWS_ACCESS_KEY_ID=$(oc get secret -n openshift-storage noobaatest -o yaml | grep -w "AWS_ACCESS_KEY_ID:" | head -n1 | awk '{print $2}' | base64 --decode)
AWS_SECRET_ACCESS_KEY=$(oc get secret -n openshift-storage noobaatest -o yaml | grep -w "AWS_SECRET_ACCESS_KEY:" | head -n1 | awk '{print $2}' | base64 --decode)
```

bucket の AWS credential keys を Secret に登録する：
```
oc create secret generic image-registry-private-configuration-user --from-literal=REGISTRY_STORAGE_S3_ACCESSKEY=${AWS_ACCESS_KEY_ID} --from-literal=REGISTRY_STORAGE_S3_SECRETKEY=${AWS_SECRET_ACCESS_KEY} --namespace openshift-image-registry
```

bucket のエンドポイントを取得する。
```
BUCKET_URL=$(oc get route s3 -n openshift-storage -o=jsonpath='{.spec.host}')
```

確認：
```
$ echo $BUCKET_URL
s3-openshift-storage.apps.ocp4.example.com
```

Ingress に TLS アクセスするための certification をダウンロードする：
```
oc extract secret/$(oc get ingresscontroller -n openshift-ingress-operator default -o json | jq '.spec.defaultCertificate.name // "router-certs-default"' -r) -n openshift-ingress --confirm
```
確認：
```
$ ls
tls.crt  tls.key
```

`tls.crt` を Config Map に入れる：
```
oc create configmap image-registry-s3-bundle --from-file=ca-bundle.crt=./tls.crt  -n openshift-config
```

上記手順で準備した bucket の URLを、Image Registry のストレージバックエンドに設定する：
```
oc patch config.image/cluster -p '{"spec":{"managementState":"Managed","replicas":2,"storage":{"managementState":"Unmanaged","s3":{"bucket":'\"${bucket_name}\"',"region":"us-east-1","regionEndpoint":'\"https://${BUCKET_URL}\"',"virtualHostedStyle":false,"encrypt":false,"trustedCA":{"name":"image-registry-s3-bundle"}}}}}' --type=merge
```

1分ほど待つと、Imager Registry Operator の `AVAILABLE` カラムが再び `True` になる。

```
$ oc get clusteroperator image-registry
NAME                                       VERSION                          AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
image-registry                             4.15.0-0.okd-2024-03-10-010116   True        False         False      94s     
```

## Image Registry の公開
今の状態だと、OKD node 上に直接入らないとレジストリにアクセスできない。
外部から利用可能にするには TLS を用いたアクセスを有効にする。

### Image Registry への Route の作成
`defaultRoute` を `true` に設定する：
```
oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"defaultRoute":true}}' --type=merge
```

しばらく待つと Image Registry のエンドポイントが作成される：
```
REGISTRY_URL=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}')
```
確認：
```
$ echo $REGISTRY_URL
default-route-openshift-image-registry.apps.ocp4.example.com
```

### TLS 設定
docker, podman などのコンテナ CLI から、TLS 経由で Image Registry アクセスできるようにする。
この設定方法はクライアント側の OS に依存する。例として Fedora系 OS, Debian 系 OS における設定方法をあげる。

#### Fedora 系 OS の場合
```
CA_DIR=/etc/pki/ca-trust/source/anchors/
oc get secret -n openshift-ingress router-certs-default -o go-template='{{index .data "tls.crt"}}' | base64 -d | sudo tee ${CA_DIR}/${REGISTRY_URL}.crt  > /dev/null
sudo update-ca-trust enable
```

#### Debian 系 OS の場合
```
CA_DIR=/etc/ssl/certs/
oc get secret -n openshift-ingress router-certs-default -o go-template='{{index .data "tls.crt"}}' | base64 -d | sudo tee ${CA_DIR}/${REGISTRY_URL}.crt  > /dev/null
sudo update-ca-certificates 
```

## レジストリの動作確認
OpenShift の `kubeadmin` ユーザの認証情報を利用して Image Registry にアクセスする。
Image Registry のエンドポイントにアクセスできることを確認する：
```
oc login -u kubeadmin -p <password_from_install_log>
REGISTRY_URL=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}')
REGISTRY_CA=${CA_DIR}/${REGISTRY_URL}.crt
curl -ks --cacert $REGISTRY_CA -u kubeadmin:$(oc whoami -t) https://$REGISTRY_URL/v2/_catalog | jq
```
出力例：
```
$ curl -ks --cacert $REGISTRY_CA -u kubeadmin:$(oc whoami -t) 
https://$REGISTRY_URL/v2/_catalog | jq
{
  "repositories": [
    "openshift/cli",
    "openshift/cli-artifacts",
    "openshift/dotnet",
    "openshift/dotnet-runtime",
    "openshift/driver-toolkit",
    "openshift/golang",
    "openshift/httpd",
    "openshift/installer",
    "openshift/installer-artifacts",
    "openshift/java",
    "openshift/java-runtime",
    "openshift/jenkins",
    "openshift/jenkins-agent-base",
    "openshift/mariadb",
    "openshift/must-gather",
    "openshift/mysql",
    "openshift/network-tools",
    "openshift/nginx",
    "openshift/nodejs",
    "openshift/oauth-proxy",
    "openshift/openliberty",
    "openshift/perl",
    "openshift/php",
    "openshift/postgresql",
    "openshift/python",
    "openshift/redis",
    "openshift/ruby",
    "openshift/tests",
    "openshift/tools",
    "openshift/ubi8-openjdk-11-runtime",
    "openshift/ubi8-openjdk-17-runtime",
    "openshift/ubi8-openjdk-8-runtime",
    "openshift/wildfly-runtime",
    "openshift/wildfly-s2i"
  ]
}
```

### コンテナイメージの push/pull
Image Registry にログインする
```
$ podman login -u kubeadmin -p $(oc whoami -t) $REGISTRY_URL
Login Succeeded!

$ podman login --get-login $REGISTRY_URL
kubeadmin
```

テスト用にプロジェクト `test-project` を作成する。
```
$ oc new-project test-project
```

次に、テスト用に push するコンテナイメージを適当に用意する。
なんでもいいのだが、例として `ubuntu` を引っ張ってくる：
```
$ podman pull docker.io/library/ubuntu
```

これを Image Registry にpush する。

:::note
push の際、イメージ名は `<project-name>/<image-name>:<tag>` のフォーマットに従う必要がある。OpenShift クラスタ 上に存在しないプロジェクト名を `<project-name>` に指定した場合はエラーになるので注意。
:::

```
$ podman push ubuntu ${REGISTRY_URL}/test-project/ubuntu:latest
Getting image source signatures
Copying blob 42d3f8788282 done  
Copying config 17c0145030 done  
Writing manifest to image destination
Error: writing manifest "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.image.config.v1+json\",\"digest\":\"sha256:17c0145030df106e60e5d99149d69810db23b869ff0d3c9d236279a5a7bbb6b3\",\"size\":2295},\"layers\":[{\"mediaType\":\"application/vnd.oci.image.layer.v1.tar+gzip\",\"digest\":\"sha256:cbd3aad7b8c34bcdfbb02d9ff325c3f75dde860a88eccb7b9def49535b729911\",\"size\":30937352}]}": uploading manifest latest to default-route-openshift-image-registry.apps.ocp4.example.com/my_project/ubuntu: denied
```

push したイメージがレジストリに登録されていることを確認する：
```
$ oc get is -n test-project ubuntu
NAMESPACE      NAME     IMAGE REPOSITORY                                                                   TAGS     UPDATED
test-project   ubuntu   default-route-openshift-image-registry.apps.ocp4.example.com/test-project/ubuntu   latest   20 seconds ago
```

最後に、`test-project` を削除する：
```
oc delete project test-project
```

project が消えると、コンテナイメージもレジストリから自動的に削除される。
次のコマンドで、Image Registry から `test-project/ubuntu` イメージが消えていることを確認する：
```
oc get is -n test-project ubuntu
```

## 参考資料

https://www.redhat.com/en/blog/configure-the-openshift-image-registry-backed-by-openshift-container-storage