OpenShift ImageStream は何をしてくれるのか

# ImageStream 概要
ImageStream[^1] は OpenShift のリソースの 1 つです。コンテナイメージそのものは含まず、コンテナイメージを参照を行うためのリソースとなります。コンテナイメージの参照を抽象化し、ImageStream とタグによって利用可能なイメージを管理します。
OpenShift DeploymentConfig に指定することができますが、ImageStream を指定せずに直接コンテナイメージの URL やタグを指定することも可能です。

# 疑問点

ここで 1つ 気になる点が出てきます。ImageStream を DeploymentConfig に指定せずに利用できるのであれば、ImageStream の存在意義とは何でしょうか？ ImageStream を利用することで、DeploymentConfig の ImageChange Trigger[^2] を利用することができますが、それだけでしょうか？ コンテナイメージ変更を契機に Pod の Updaet を行わない場合は不要となるのでしょうか。

# ImageStream を利用しない場合
## DeploymentConfig によるコンテナのデプロイ

サンプルとして OpenShift で私がよくデプロイする ruby-ex アプリケーションを利用します。デプロイに必要なリソースは `oc new-app` コマンドで予め作成しておきます。

```shell
oc new-app centos/ruby-25-centos7~https://github.com/sclorg/ruby-ex.git
```

コマンド実行完了後、利用しない DeploymentConfig を削除しておきます。

```shell
oc delete dc ruby-ex
```

ここで ImageStream を利用しない方法で DeploymentConfig を定義してみます。

```
oc create -f ruby-ex-without-is.yaml
```

ruby-ex-without-is.yaml の詳細は次のとおりです。spec.template.containers[0].image に直接コンテナイメージの URL とタグが記載されています。

```dc/ruby-ex-without-is.yaml
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  creationTimestamp: null
  generation: 1
  labels:
    app: ruby-ex
  name: ruby-ex
  selfLink: /apis/apps.openshift.io/v1/namespaces/without-is/deploymentconfigs/ruby-ex
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    app: ruby-ex
    deploymentconfig: ruby-ex
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: ruby-ex
        deploymentconfig: ruby-ex
    spec:
      containers:
      - image: docker-registry.default.svc:5000/without-is/ruby-ex:latest
        imagePullPolicy: Always
        name: ruby-ex
        ports:
        - containerPort: 8080
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  test: false
  triggers:
  - type: ConfigChange
status:
  availableReplicas: 0
  latestVersion: 0
  observedGeneration: 0
  replicas: 0
  unavailableReplicas: 0
  updatedReplicas: 0
```

Pod が正常にデプロイされていることを確認します。

```shell
# oc get pod
NAME              READY     STATUS      RESTARTS   AGE
ruby-ex-1-build   0/1       Completed   0          4m
ruby-ex-4-5ffss   1/1       Running     0          32s
```

レプリカ数を 2 に変更してみます。

```shell
# oc scale dc/ruby-ex --replicas 2
```

コンテナのイメージIDを確認します。とちらの Pod も同じ Image ID を参照していることがわかります。

```shell
]# oc describe pod -l app=ruby-ex | grep "Image ID:"
    Image ID:       docker-pullable://docker-registry.default.svc:5000/sample-is/ruby-ex@sha256:ea3dccca08d1cedb5f42d952519c40b6bc8db9b857d280b60180017107f6fc5a
    Image ID:       docker-pullable://docker-registry.default.svc:5000/sample-is/ruby-ex@sha256:ea3dccca08d1cedb5f42d952519c40b6bc8db9b857d280b60180017107f6fc5a
```

ここでコンテナをビルドし直します。

```shell
# oc start-build ruby-ex
```

ビルド完了後にレプリカ数を 3 へ変更し、Pod が参照している Image ID を確認してみます。ことなるコンテナイメージを参照している Pod が存在していることがわかります。

```shell
# oc describe pod -l app=ruby-ex | grep "Image ID:"
    Image ID:       docker-pullable://docker-registry.default.svc:5000/sample-is/ruby-ex@sha256:ea3dccca08d1cedb5f42d952519c40b6bc8db9b857d280b60180017107f6fc5a
    Image ID:       docker-pullable://docker-registry.default.svc:5000/sample-is/ruby-ex@sha256:9e0dfb3183cf741b3092d5482467e4a327040e5cc9646017828213dc438f4665
    Image ID:       docker-pullable://docker-registry.default.svc:5000/sample-is/ruby-ex@sha256:ea3dccca08d1cedb5f42d952519c40b6bc8db9b857d280b60180017107f6fc5a
```

何が起きたのでしょうか。DeploymentConfig に指定したコンテナイメージの URL とタグは変更していません。

そうです。タグは変えていませんが、先程実施したコンテナビルドにより、タグが参照しているイメージが変更となりました。

# ImageStream を利用した場合
## ImageStream を指定した場合の DeploymentConfig

まず、ImageStream を利用した DeploymentConfig を作成します。ImageStream を利用した DeploymentConfig の一番簡単な作成方法は `oc new-app` を利用することです。

oc コマンドでプロジェクト作成時にサンプルとして出力される例をそのまま利用します。

```shell
oc new-app centos/ruby-25-centos7~https://github.com/sclorg/ruby-ex.git
```

```shell:出力例
--> Found Docker image dc0e72c (4 weeks old) from Docker Hub for "centos/ruby-25-centos7"

    Ruby 2.5 
    -------- 
    Ruby 2.5 available as container is a base platform for building and running various Ruby 2.5 applications and frameworks. Ruby is the interpreted scripting language for quick and easy object-oriented programming. It has many features to process text files and to do system management tasks (as in Perl). It is simple, straight-forward, and extensible.

    Tags: builder, ruby, ruby25, rh-ruby25

    * An image stream tag will be created as "ruby-25-centos7:latest" that will track the source image
    * A source build using source code from https://github.com/sclorg/ruby-ex.git will be created
      * The resulting image will be pushed to image stream tag "ruby-ex:latest"
      * Every time "ruby-25-centos7:latest" changes a new build will be triggered
    * This image will be deployed in deployment config "ruby-ex"
    * Port 8080/tcp will be load balanced by service "ruby-ex"
      * Other containers can access this service through the hostname "ruby-ex"

--> Creating resources ...
    imagestream.image.openshift.io "ruby-25-centos7" created
    imagestream.image.openshift.io "ruby-ex" created
    buildconfig.build.openshift.io "ruby-ex" created
    deploymentconfig.apps.openshift.io "ruby-ex" created
    service "ruby-ex" created
--> Success
    Build scheduled, use 'oc logs -f bc/ruby-ex' to track its progress.
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/ruby-ex' 
    Run 'oc status' to view your app.
```

Image更新を検出して勝手にアップデートされないようにトリガーはオフにします。

```
oc set triggers dc/ruby-ex  --manual
```

設定が完了した DeploymentConfig は次のとおりです。ImageStream がない場合の違いがあります。Image に指定されているのがURLとタグではなく、URLとハッシュタグとなりました。
また、triggers が設定されており、ImageChange に ImageStream とタグが指定されています。

```dc/ruby-ex.yaml
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  annotations:
    openshift.io/generated-by: OpenShiftNewApp
  creationTimestamp: null
  generation: 1
  labels:
    app: ruby-ex
  name: ruby-ex
  selfLink: /apis/apps.openshift.io/v1/namespaces/is/deploymentconfigs/ruby-ex
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    app: ruby-ex
    deploymentconfig: ruby-ex
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      annotations:
        openshift.io/generated-by: OpenShiftNewApp
      creationTimestamp: null
      labels:
        app: ruby-ex
        deploymentconfig: ruby-ex
    spec:
      containers:
      - image: docker-registry.default.svc:5000/is/ruby-ex@sha256:ad60376e709fec2764d249327cc8a1cb6f5b085c392f95b8c786ff67875e74bf
        imagePullPolicy: Always
        name: ruby-ex
        ports:
        - containerPort: 8080
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  test: false
  triggers:
  - imageChangeParams:
      containerNames:
      - ruby-ex
      from:
        kind: ImageStreamTag
        name: ruby-ex:latest
        namespace: is
    type: ImageChange
status:
  availableReplicas: 0
  latestVersion: 0
  observedGeneration: 0
  replicas: 0
  unavailableReplicas: 0
  updatedReplicas: 0
```

Pod のレプリカ数を 2 に変更します。
```shell
# oc scale dc/ruby-ex --replicas 2
```

Pod のイメージIDを確認します。どちらも同じイメージが参照されています。
```shell
 oc describe pod -l app=ruby-ex | grep "Image ID:"
    Image ID:       docker-pullable://docker-registry.default.svc:5000/is/ruby-ex@sha256:8ebf0e1e2016241077934270246353d15742afa2d45b521d77fac399c9e0e32b
    Image ID:       docker-pullable://docker-registry.default.svc:5000/is/ruby-ex@sha256:8ebf0e1e2016241077934270246353d15742afa2d45b521d77fac399c9e0e32b
```

コンテナイメージをビルドします。

```shell
# oc start-build ruby-ex
```

レプリカ数を 3 に変更した後に、コンテナイメージの ID を確認します。すべて同じイメージを参照していることがわかります。

```shell
# oc describe pod -l app=ruby-ex | grep "Image ID:"
    Image ID:       docker-pullable://docker-registry.default.svc:5000/is/ruby-ex@sha256:e7e2ce3f129d1d98b615ed8efd157a281a0aeee5760efb04e074c3cda39df78b
    Image ID:       docker-pullable://docker-registry.default.svc:5000/is/ruby-ex@sha256:e7e2ce3f129d1d98b615ed8efd157a281a0aeee5760efb04e074c3cda39df78b
    Image ID:       docker-pullable://docker-registry.default.svc:5000/is/ruby-ex@sha256:e7e2ce3f129d1d98b615ed8efd157a281a0aeee5760efb04e074c3cda39df78b
```


# まとめ

タグを変えずにコンテナイメージをビルドした場合、Pod をスケールさせた場合異なるバージョンのイメージを参照してしまう事象を確認しました。これを回避するためには、タグを分けるという手段もありますが、ImageStream を利用すればハッシュ値を利用してこの問題を解決することも可能であるということがわかりました。

今回は ImageSteam の1つの役割について確認しました。その他の ImageStream の詳細については製品ドキュメント[^3]を参照して貰えればと思います。


[^1]: [OpenShift Container Platform 4.2 - Understanding containers, images, and imagestreams](https://docs.openshift.com/container-platform/4.2/openshift_images/images-understand.html)
[^2]: [OpenShift Container Platform 4.2 - Deployment triggers](https://docs.openshift.com/container-platform/4.2/applications/deployments/managing-deployment-processes.html#deployments-triggers_deployment-operations)
[^3]: [OpenShift Container Platform 4.2 - Using imagestreams](https://docs.openshift.com/container-platform/4.2/openshift_images/images-understand.html#images-imagestream-use_images-understand)