IBMCloud Openshift 内部イメージレジストリにDockerのnginxコンテナイメージを登録して利用する

## 概要
IBMCloudでOpenshiftの内部イメージにdockerhubから取得したnginxをアップロードし、openshift環境でnginxコンテナを動作させるための手順・ログを備忘録も兼ねて記事としています。

以下のIBMCloud Docsを参考に実行しております
[内部レジストリーのためのセキュアな外部経路のセットアップ](https://cloud.ibm.com/docs/openshift?topic=openshift-registry&locale=ja#route_internal_registry)

## 環境情報
以下の環境での操作を実施しました。
* IBMCloud Openshift 4.13.43 on VPC
* Macbook(M1)

## 作業手順

* Step1. OpenShiftにログイン
    * IBMCloud Portal -> Openshift -> 対象クラスタ -> Openshift コンソールを選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703075/590632c5-aba4-e1d4-637f-00e7d301937f.png)
    * コマンドラインツールを選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703075/ce228aa9-c050-d93e-6c3a-be8c32143352.png)

    * 'ログインコマンドをコピー'を選択し、'display token'をクリック
    * 該当Clusterへのログインコマンドをコピーして操作端末でログインする
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703075/692e54a1-be01-34c4-d220-7c9b351e3678.png)

```sh
% oc login --token=sha256~0g_kxEI8rPoxxxxxxxxxxxxxxxxxxxdYY --server=https://xxxxxx.jp-tok.containers.cloud.ibm.com:30916
Logged into "https://xxxxxx.jp-tok.containers.cloud.ibm.com:30916" as "IAM#xxxxxxxxxxx@xxx.com" using the token provided.

You have access to 65 projects, the list has been suppressed. You can list all projects with 'oc projects'

Using project "default".
%
```

* Step2. Openshift上にimage-registryサービスが存在することを確認
```sh
% oc get svc -n openshift-image-registry
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
image-registry            ClusterIP   172.21.164.24   <none>        5000/TCP    105m
image-registry-operator   ClusterIP   None            <none>        60000/TCP   129m
%
```

* Step3. image-registryサービスのためにTLS終端処理を使用したセキュアな経路を作成する(Dockerファイルをアップロードするための経路）

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

* Step4. ルート情報を取得（Host部分のFQDNを後ほど利用する）
```sh
% oc get route image-registry -n openshift-image-registry
NAME             HOST/PORT                                                                                                                                   PATH   SERVICES         PORT       TERMINATION   WILDCARD
image-registry   image-registry-openshift-image-registry.acs-paas-oc-cluster3-jp-t-d13c20a30062048xxxxxxxxxxxxxxxxxx.jp-tok.containers.appdomain.cloud          image-registry   5000-tcp   reencrypt     None
%
```

* Step5. HAエンドポイントにアクセスするクライアントが同じイメージレジストリ（Node?）接続先に振られるようにLB設定をSource指定にしておく

    * image-registryファイルをoc editで編集する

    ```sh
    $ oc edit route image-registry -n openshift-image-registry
    ```
    * annotationsの下に`haproxy.router.openshift.io/balance: source`の一行を追記
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703075/5771e7cb-2247-1ff0-758f-377f3b1cec50.png)

* Step7. Step5のホスト名（ルート情報）を使用してOpenshiftのレジストリーにログイン
```sh
% docker login -u $(oc whoami) -p $(oc whoami -t) image-registry-openshift-image-registry.acs-paas-oc-cluster3-jp-t-d13c20a3006204xxxxxxxxxxxxxxx-0000.jp-tok.containers.appdomain.cloud
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
%
```


* Step6. DockerHubからnginxイメージをPullする

    * まず、ローカルマシンにNginxのイメージをPullします
(MAC端末がM1(ARMアーキテクチャ)CPUのため、platform指定を忘れずに行う)

```sh
$ docker pull --platform linux/x86_64 nginx:latest
```

:::note warn
DockerHubからnginxイメージをPullするには、Dockerにログインしている必要があります。
[公式サイトを参照](https://docs.docker.jp/docker-hub/accounts.html)
:::

* Step7. openshiftでnginxを動かすプロジェクトを作成

```sh
% oc new-project nginx-project
```

* Step8. LocalPCにあるnginxイメージファイルへのイメージタグ付けとOpenshiftへのPush
```sh
$ docker tag nginx:latest image-registry-openshift-image-registry.xxx-paas-oc-cluster3-jp-t-d13c20a300620486546182a0effecxxxxxxxx.jp-tok.containers.appdomain.cloud/nginx-project/nginx:latest
$ docker push image-registry-openshift-image-registry.xxx-paas-oc-cluster3-jp-t-d13c20a300620486546182a0effecxxxxxxxx.jp-tok.containers.appdomain.cloud/nginx-project/nginx:latest
```
* Step9. Push後、Openshift上にnginxのイメージストリームが作成されていることを確認

```sh
%  oc get imagestream
NAME    IMAGE REPOSITORY                                                       TAGS     UPDATED
nginx   image-registry.openshift-image-registry.svc:5000/nginx-project/nginx   latest   3 minutes ago
%
```

* Step10. プロジェクトのデプロイメントで、この内部レジストリーからイメージをプルできるように、内部レジストリーにアクセスするための資格情報を保持するイメージ・プル・シークレットをプロジェクト内に作成

    * デフォルトのイメージプルで利用されているシークレット情報をメモ
     ->  "Image pull secrets: default-dockercfg-bd4kd"
```sh
% oc describe sa default
Name:                default
Namespace:           nginx-project
Labels:              <none>
Annotations:         <none>
Image pull secrets:  default-dockercfg-bd4kd
Mountable secrets:   default-dockercfg-bd4kd
Tokens:              default-token-grl5w
Events:              <none>
%
```


* Step11. 構成ファイルの data フィールドから、エンコードされたシークレット情報を取得(Dataフィールドは長文情報のためここでは出力結果は省略しています)

```sh
$ oc get secret default-dockercfg-bd4kd -o yaml
```

* Step12. 出力されたdataフィールドの値をbase64でデコードします

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

* Step13. 上記デコードした情報を利用し、内部レジストリーの新規ImagePullSecretを作成する

:::note warn
公式Docs(2024/7/4時点)では"oc create secret image-registry xxxx"とありますが、エラーになりコマンド構文としては、"oc create secret docker-registry xxxx"が正しいと思われます。
:::

```sh
% oc create secret docker-registry internal-registry --namespace nginx-project --docker-server image-registry-openshift-image-registry.xxx-paas-oc-cluster2-jp-t-d13c20a300620486546182a0xxxxxxxxxx-0000.jp-tok.containers.appdomain.cloud:5000 --docker-username serviceaccount --docker-password <eyxxxxxxxxxxxxxxx> --docker-email serviceaccount@example.org
secret/internal-registry created
eranaoki@Naokis-MBP ~ %
eranaoki@Naokis-MBP ~ % oc get secret -n nginx-project
NAME                       TYPE                                  DATA   AGE
builder-dockercfg-z6gsn    kubernetes.io/dockercfg               1      126m
builder-token-9g6nx        kubernetes.io/service-account-token   4      126m
 (一部省略)
internal-registry          kubernetes.io/dockerconfigjson        1      7s
%
```

* Step14. イメージ・プル・シークレットをプロジェクトのデフォルトの"サービス・アカウント"に追加

```sh
oc patch -n nginx-project serviceaccount/default --type='json' -p='[{"op":"add","path":"/imagePullSecrets/-","value":{"name":"<image_pull_secret_name>"}}]'
```

:::note info
（補足説明）
サービス・アカウントとは、通常のUserとは別に、Podや各種コンポーネントがAPI呼び出しを行うために設計されたKubernetesのオブジェクトです。Jenkinsなどでアプリケーションを自動化する際にも利用されます。普段使っているPodにも必ず1つのService Accountが紐づいています。Pod作成時にService Accountを明示的に指定しなかった場合そのnamespaceのdefaultという名前のService Accountが紐づけられます。
:::

* Step15. サービスアカウント"default"に対して anyuid SCC を適用

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

:::note alert
要確認：
OpenShiftでは、Security Context Constraints (SCC) を使用して、ポッドがどのように実行されるかを制御します。デフォルトでは、ポッドは特定の制限の下で実行されますが、これにより一部のアプリケーション（特にrootユーザーとしての実行を必要とするnginxのようなソフトウェア）が正常に動作しないことがあります。
今回はNginxを起動するためにサービス・アカウント("default")でroot権限にてコンテナ起動ができるようにするため上記コマンドを実行しています。セキュリティ設計の考慮は別途ご検討が必要な点に注意ください。
:::

* Step16. nginxを内部イメージレジストリから起動する (YAMLファイルで定義)
（今回はimageに先述の"oc get imagestream"で確認したイメージストリーム"image-registry.openshift-image-registry.svc:5000/nginx-project/nginx:latest"を指定します)

```yaml
#nginxを内部イメージレジストリから起動し、80portをListenさせる
#nginx-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: >-
          image-registry.openshift-image-registry.svc:5000/nginx-project/nginx:latest
        ports:
        - containerPort: 80
        securityContext:
          runAsUser: 0
          runAsGroup: 0
        volumeMounts:
        - name: nginx-cache
          mountPath: /var/cache/nginx
      volumes:
      - name: nginx-cache
        emptyDir: {}
```

* Step17. nginxのPod起動および確認

```sh
# Project(namespace)をnginx-projectに切替
% oc project nginx-project
Now using project "nginx-project" on server "https://c115-e.jp-tok.containers.cloud.ibm.com:30916".
%

# nginx pod起動
% oc apply -f nginx-test.yml 
deployment.apps/nginx-deployment created

# podの確認
# 172.17.139.252のIPアドレスで稼働していることが確認できる
% oc get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
nginx-deployment-8cfddb7b5-dcbmw   1/1     Running   0          13s   172.17.139.252   10.244.0.5   <none>           <none>
% 
```

* Step18. nginx稼働確認のためにalpineイメージのPodを作成し、curl確認を行う
```sh
# alpine Podを起動
% oc run alpine-temp --image=alpine --command -- sleep 3600
pod/alpine-temp created 
% oc get pod
NAME                               READY   STATUS    RESTARTS   AGE
alpine-temp                        1/1     Running   0          6s
nginx-deployment-8cfddb7b5-dcbmw   1/1     Running   0          17m
%

# alpineへログインし、Curlを追加
% oc exec -it alpine-temp -- sh
/ # 
/ # apk add curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
(1/10) Installing ca-certificates (20240226-r0)
(2/10) Installing brotli-libs (1.1.0-r2)
(3/10) Installing c-ares (1.28.1-r0)
(4/10) Installing libunistring (1.2-r0)
(5/10) Installing libidn2 (2.3.7-r0)
(6/10) Installing nghttp2-libs (1.62.1-r0)
(7/10) Installing libpsl (0.21.5-r1)
(8/10) Installing zstd-libs (1.5.6-r0)
(9/10) Installing libcurl (8.8.0-r0)
(10/10) Installing curl (8.8.0-r0)
Executing busybox-1.36.1-r29.trigger
Executing ca-certificates-20240226-r0.trigger
OK: 13 MiB in 24 packages
/ # 


# curlでのnginx稼働確認し、"Welcome to nginx!"のHTML情報が取得できることを確認

/ # curl http://172.17.139.252
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ # 

```

これで内部イメージレジストリからnginxが起動できるようになりました。