Red Hat OpenShift on IBM Cloudで、内部レジストリに外部からアクセスする

# はじめに
Red Hat OpenShift on IBM Cloudでは、コンテナレジストリとしてIBM Cloud Container Registryを使用するケースが多いと思います。ただ、Container Registryを使用できないケースでは、OpenShift内でデフォルトでセットアップされているOpenShift Container Registry（OCR）を使用することになります。

OCRは内部からのアクセスが可能であるものの、例えばローカルの端末でビルドしたイメージをpushするといったような外部からのアクセスができない状態になっています。

この記事では、OCRに外部からアクセス（push）できるように設定し、さらに、クラスター内でOCRからイメージをpullできるように設定する手順を紹介します。この記事の内容は、基本的に「[IBM Cloudドキュメント：内部レジストリーのためのセキュアな外部経路のセットアップ](https://cloud.ibm.com/docs/openshift?topic=openshift-registry&locale=ja#route_internal_registry)」に基づいています。


# 前提環境
・Red Hat OpenShift on IBM Cloud (OpenShiftバージョン：4.10.22_1528）
・OpenShiftクラスターに対して、IBM Cloud IAMの「マネージャ」サービス役割を保持していること


# 内部レジストリに外部からpushできるようにする
openshift-image-registry プロジェクトに、``image-registry``サービスが存在することを確認します。

```bash
❯ oc get svc -n openshift-image-registry
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
image-registry            ClusterIP   xxx.xxx.xx.xxx   <none>        5000/TCP    6h33m
image-registry-operator   ClusterIP   None             <none>        60000/TCP   6h50m
```

``image-registry``サービスに対して、暗号化されたrouteを作成します。ここでは``reencrypt``（クライアント - route間で暗号化を一旦終端し、再度暗号化してroute - 内部レジストリ間もセキュアにする）を指定してrouteを作成します。

```bash
❯ oc create route reencrypt --service=image-registry -n openshift-image-registry
route.route.openshift.io/image-registry created
```

作成されたrouteに割り当てられたホスト名、ポートを確認します。

```bash
❯ oc get route image-registry -n openshift-image-registry
NAME             HOST/PORT                                                                                                                                   PATH   SERVICES         PORT       TERMINATION   WILDCARD
image-registry   image-registry-openshift-image-registry.xxxxx.jp-tok.containers.appdomain.cloud          image-registry   5000-tcp   reencrypt     None
```

作成したrouteを``oc edit``で編集し、ロードバランスの方式を``source``に変更します。これにより、同じIPアドレスからのアクセスは同じPodに到達するようになります。

```bash
oc edit route image-registry -n openshift-image-registry
```

編集箇所は（コメントを含めて）10行目あたりで、``metadata.annotations``に``haproxy.router.openshift.io/balance: source``の行を追加して保存します。

```yaml
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  annotations:
    openshift.io/host.generated: "true"
    haproxy.router.openshift.io/balance: source    # 左記の内容を追加する
```

以上により外部から内部レジストリにアクセスできるようになったので、``docker login``によりログインします。ホスト名・ポートは、上記のrouteで確認したホスト名・ポートを使用します。ユーザ名、パスワードは、OpenShiftへのログインユーザ、パスワードを使用します。

```bash
❯ docker login -u $(oc whoami) -p $(oc whoami -t) image-registry-openshift-image-registry.xxxxxxxxxxx.jp-tok.containers.appdomain.cloud
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
```

ここでは、イメージをpushし、デプロイして稼働させるためのサンプルプロジェクトを``sample``という名前で作成します。

```bash
❯ oc new-project sample                                                                                          
Now using project "sample" on server "https://c115-e.jp-tok.containers.cloud.ibm.com:30645".

You can add applications to this project with the 'new-app' command. For example, try:

    oc new-app rails-postgresql-example

to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:

    kubectl create deployment hello-node --image=k8s.gcr.io/e2e-test-images/agnhost:2.33 -- /agnhost serve-hostname
```

pushするイメージとしてnginxを使用します（``--platform``オプションは、ローカル端末がM1 Mac(arm64)のために付けています。amd64のCPUの場合は不要です）。

```
❯ docker pull --platform linux/x86_64 nginx:latest
latest: Pulling from library/nginx
Digest: sha256:ecc068890de55a75f1a32cc8063e79f90f0b043d70c5fcf28f1713395a4b3d49
Status: Image is up to date for nginx:latest
docker.io/library/nginx:latest
```

OCRにpushするための準備として、``docker tag``コマンドでタグを付けます。ホスト名の部分は作成したrouteで確認したホスト名です。ホスト名に続けて、``/<プロジェクト名>（この例ではsample）/nginx:latest`` を指定します。

```
❯ docker tag nginx:latest image-registry-openshift-image-registry.xxxxxxx.jp-tok.containers.appdomain.cloud/sample/nginx:latest
```

タグを付けたイメージをpushします。

```
❯ docker push image-registry-openshift-image-registry.xxxxxxx.jp-tok.containers.appdomain.cloud/sample/nginx:latest 
The push refers to repository [image-registry-openshift-image-registry.xxxxxxx.jp-tok.containers.appdomain.cloud/sample/nginx]
b539cf60d7bb: Pushed 
bdc7a32279cc: Pushed 
f91d0987b144: Pushed 
3a89c8160a43: Pushed 
e3257a399753: Pushed 
92a4e8a3140f: Pushed 
latest: digest: sha256:f26fbadb0acab4a21ecb4e337a326907e61fbec36c9a9b52e725669d99ed1261 size: 1570
```

イメージをpushすると、``sample``プロジェクトにImageStreamが作成されるため、それを確認します。

```
❯ oc get imagestream                                     
NAME    IMAGE REPOSITORY                                                TAGS     UPDATED
nginx   image-registry.openshift-image-registry.svc:5000/sample/nginx   latest   29 seconds ago
```


# クラスターが内部レジストリからイメージをpullできるようにする
次に、pushしたイメージをpullできるように設定します。

現在のプロジェクトが先ほど作成した``sample``であることを確認します。
```
❯ oc project        
Using project "sample" on server "https://c115-e.jp-tok.containers.cloud.ibm.com:xxxxx".
```

``default``サービスアカウントの詳細を表示し、``Image pull secrets``で指定されているSecretを確認します。

```
❯ oc describe sa default            
Name:                default
Namespace:           sample
Labels:              <none>
Annotations:         <none>
Image pull secrets:  default-dockercfg-zzjd4
Mountable secrets:   default-token-bqklv
                     default-dockercfg-zzjd4
Tokens:              default-token-bqklv
                     default-token-wx8n9
Events:              <none>
```

上記で確認したSecretをYAML形式で出力し、``.dockercfg``の値を確認します。

```
❯ oc get secret default-dockercfg-zzjd4 -o yaml                                                                  
apiVersion: v1
data:
  .dockercfg:xxxxx
```

``.dockercfg``の値はBase64でエンコードされているため、それをデコードします（``echo``コマンドに渡しているのが``.dockercfg``の値です）。デコードすることで、イメージをpullするために必要なusername、passwordを得ることができます。

```
echo "<ey...=>" | base64 -D


{"172.xx.xxx.xxx:5000":{"username":"serviceaccount","password":"ey......
```

上記で取得した「routeのホスト名」、「username」、「password」の値を用いて、内部レジストリ用の新たなImage Pull Secretを作成します。ここでは``internal-registry``という名前のSecretを作成します。各オプションではそれぞれ以下を指定します。

- ``--docker-server`` 　：　routeのホスト名＋``:5000``（ポート番号）
- ``--docker-username``　：　上記のusernameの値
- ``--docker-password``　：　上記のpasswordの値
- ``--docker-email``　：　適当なメールアドレス（例：``a@b.c``）

```
oc create secret image-registry internal-registry --namespace sample --docker-server image-registry-openshift-image-registry.xxxxxxx.jp-tok.containers.appdomain.cloud:5000 --docker-username serviceaccount --docker-password <eyJ...> --docker-email a@b.c
```

``default``サービスアカウントに、上記で作成したImage Pull Secretを追加します。

```
❯ oc patch -n sample serviceaccount/default --type='json' -p='[{"op":"add","path":"/imagePullSecrets/-","value":{"name":"internal-registry"}}]'
serviceaccount/default patched
```

nginxのイメージでは、ポート80を使用するためにroot権限が必要になります。しかし、OpenShiftはセキュリティ上の理由からコンテナで設定されているユーザではなく、ランダムなUIDで実行するため、ポート80が使用できません。それを回避するため、``anyuid``SCCを``default``サービスアカウントに付与し、コンテナで設定したユーザでの実行を許可します。

```
❯ oc adm policy add-scc-to-user anyuid -z default
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "default"
```

# nginxコンテナのデプロイ
それではnginxコンテナをデプロイしてみます。

OpenShiftのWebコンソールにアクセスし、「Developer」パースペクティブを選択し、「追加」→「コンテナイメージ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/c0e0da11-ed13-1fc3-e945-10ec025ef7c5.png)


「内部レジストリーからのイメージストリームタグ」を選択し、プロジェクト、イメージストリーム、タグに、それぞれ「sample」、「nginx」、「latest」を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/d48e4602-3e4c-8083-df80-8516bd8d3056.png)

画面を下にスクロールし、他の項目はデフォルト値のままで「作成」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/8ee1c28c-4f27-72ae-7e00-adc90dfe1ae3.png)

しばらくするとデプロイが完了するので、「トポロジー」のアイコンの右上部分をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/6a9341dc-aba9-5088-c455-99ca07b5816a.png)

nginxのWelcomページが表示されることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/4eb89514-6619-b26b-e48a-7761b89fa277.png)