⑰１日１０分で理解するコンテナ技術入門 - OKEを使ったサービス公開 -

前回までで以下のようなリソースについて紹介しました。
- [Pod](https://qiita.com/goroneko/items/c0161761d644e0bd5502)
- [ReplicaSet](https://qiita.com/goroneko/items/ab451647b7fa5acd1b80)
- [Deployment](https://qiita.com/goroneko/items/385bc6cce0f6cb4507b3#%E3%81%BE%E3%81%A8%E3%82%81)
- [Service](https://qiita.com/goroneko/items/6fe7a7a4fbf861fada1b)

今回はこれらの知識を使って、簡単なウェブサービスをパブリッククラウドで公開してみたいと思います。

Kubernetesをパブリッククラウドで利用する場合、各社のマネージドサービスを利用することができます。代表的なKubernetesのマネージドサービスは、
- GKE (Google)
- EKS (AWS)
- AKS (Azure)
- OKE (OCI)

などがあります。
今回はOracleのOKEを使って、自作のサービスをインターネットに公開してみます。なお、前回までで紹介した知識だけで公開するので、本番想定ではない点については注意してください。あくまで各コンポーネントの動きを理解する目的です。

[今回使用するコード](https://github.com/sogawa-yk/simple-knowledge-sharing-platform/tree/17-article)

# OKEクラスターを作成する
OKEのクラスターを作成して、ターミナルから`kubectl`コマンドが利用できるようにします。
## OKEクラスターのプロビジョニング
OCIのホーム画面に移動したら、右上のハンバーガーメニューから、「開発者サービス」->「 Kubernetesクラスタ（OKE）」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/0511c4ea-2912-6133-23b5-8ac602a1295b.png)

「クラスタの作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/605b30fe-a653-bc47-185e-b1dce17418e1.png)

「クイック作成」が選択されていることを確認して、「送信」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/5749221b-7175-c0a7-4136-7666414f3df9.png)

ここ以降は、**すべてデフォルトのまま**次に進みます（もしサービス制限の関係でインスタンスが3つ用意できない場合、ノード数を減らしてください）。

下のような画面になったら「閉じる」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/4d1b9775-0900-5ae8-608f-2376caa6e7dc.png)

下のような画面になるので、マークが緑色になるのを待ちます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/e1fa7ab2-f15c-4331-4436-652b0cc13fa8.png)

以下のようにマークが緑色になったらプロビジョニング完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/c880352e-8c93-a7a7-c214-9799964cb46c.png)

## アクセス確認
`kubectl`をインストールしている端末から、アクセス確認を行います。

下の画面から、「クラスタのアクセス」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/b3aa3858-d7f2-9a63-7899-98aa758577a2.png)

「ローカル・アクセス」を選択し、表示される手順に従います。なお、エンドポイントは「パブリックエンドポイント」の方で操作を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/5aa3f536-0ed8-5f3d-5131-02104b12878f.png)

`kubectl  get nodes`コマンドを実行して、正しくノードの情報がとれていれば成功です。
```bash
$ kubectl get nodes
NAME          STATUS   ROLES   AGE   VERSION
10.0.10.122   Ready    node    19m   v1.29.1-6+b511b8ca8a18c5
10.0.10.42    Ready    node    19m   v1.29.1-6+b511b8ca8a18c5
10.0.10.55    Ready    node    19m   v1.29.1-6+b511b8ca8a18c5
```

# コンテナイメージのリモートレポジトリを作成する
自作のアプリケーションをKubernetesクラスターで動かす場合、以下のようなイメージでコンテナイメージをやり取りします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/0b3c7c03-e839-faab-0dec-fa96bc1b33b8.png)
1. 作成したアプリケーションのコンテナイメージをリモートレポジトリ（この場合はOCIR）にプッシュ
1. アプリケーションをデプロイするためのマニフェストファイルをクラスターに適用（マニフェストファイルにはイメージをプッシュしたレポジトリの情報が記載されている）
1. クラスターは、マニフェストファイルを適用するうえで、レポジトリからイメージをプルしてくる

このような動作になるため、リモートレポジトリを作成する必要があります。OCIでは、OCIRというサービスがあるのでそれを利用します。

## レポジトリを作成

OCIのコンソールに移動し、左上のハンバーガーメニューから、「開発者サービス」、「コンテナ・レジストリ」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/27bff560-faf2-6d1a-6b38-82c47f85fe55.png)

まず、ウェブサーバのイメージをプッシュするリポジトリを作成します。

「リポジトリの作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/c664b23e-c71b-3c28-0465-40c88de87652.png)

ご自身のコンパートメントが選択されているかを確認します。アクセスは「パブリック」にし、リポジトリ名はお好きな値を入力します。私は`4qiita_sample/web`としました。入力が完了したら「作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/636f25e7-0579-f28f-a631-1491b56e9ae5.png)

次に、DBのイメージをプッシュするリポジトリを作成します。
同様の手順で、名前を`4qiita_sample/db`として作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/de754c55-cac9-c335-2ffc-dbe070f0a6b0.png)

以上でコンテナイメージのリモートレポジトリの作成は完了です。

:::note info
**OCIRについて**
OCIRは一般的なコンテナレジストリと少し仕様が異なるのでここで説明しておきたいと思います。
一般的なコンテナレジストリでは、レポジトリを作成し、その中で複数目的のイメージを配置することができると思います（例えば以下のような構成）。
```bash
└── 4qiita-sample
    ├── db
    │   ├── 1.1
    │   ├── 1.2
    │   └── latest
    └── web
        ├── 1.1
        ├── 1.2
        └── latest
```
OCIRでは、このように同一レポジトリ内に複数の目的のイメージを配置することができません。もう少し正確に言うと、リポジトリ内はディレクトリのような構造を取ることができません（同一リポジトリ内のイメージはタグでしか区別することができない）。
そのため、OCIRで複数イメージを使ったサービスを管理する場合、以下のように構成する必要があります。
```bash
├── 4qiita-sample/db （リポジトリ1）
|   ├── 1.1
|   ├── 1.2
|   └── latest
└── 4qiita-sample/web（リポジトリ2）
    ├── 1.1
    ├── 1.2
    └── latest
```
このような仕様のため、上記手順では2つのリポジトリ（web用とdb用）を作成しました。
:::


## レポジトリアクセスのために認証トークンを取得
[以前の記事](https://qiita.com/goroneko/items/413f8477e449da712137)でも説明しましたが、レポジトリアクセスにはOCIのユーザー名と認証トークンを利用しますので、取得してメモなどに控えておいてください。

## レポジトリにログイン
`docker login`コマンドでログインしてください。詳細は[以前の記事](https://qiita.com/goroneko/items/413f8477e449da712137)で確認してください。

# アプリケーションのイメージをOCIRにプッシュ
それでは、アプリケーションイメージをOCIRにプッシュしたいと思います。
まず、今回使用するアプリケーションのソースコードをダウンロードします。
```bash
$ git clone git@github.com:sogawa-yk/simple-knowledge-sharing-platform.git
$ cd simple-knowledge-sharing-platform
$ git checkout tags/9-article -b 17-article-branch
```
## webサーバのイメージをプッシュ
以下の手順でビルド・プッシュを行います。
なお、`<region-code>`はご自身が作成したOCIRのリージョンコードを[ここ](https://docs.oracle.com/ja-jp/iaas/Content/General/Concepts/regions.htm)のリージョンキーをみて調べてください。また、リージョンキーは小文字で入力します。
`<tenancy-namespace>`は、OCIコンソール画面の右上のプロファイルアイコンをクリックし、「テナンシ」を選択、遷移した画面にある「オブジェクト・ストレージ・ネームスペース」の値を入力します。
```bash
$ cd web # webサーバ用のディレクトリに移動
$ docker build -t simple-knowledge-sharing-platform/web .
$ docker tag simple-knowledge-sharing-platform/web:latest <region-code>.ocir.io/<tenancy-namespace>/4qiita-sample/web:latest
$ docker push <region-code>.ocir.io/<tenancy-namespace>/4qiita-sample/web:latest
```

## dbのイメージをプッシュ
以下の手順でビルド・プッシュを行います。
```bash
$ cd ../db # db用のディレクトリに移動
$ docker build -t simple-knowledge-sharing-platform/db .
$ docker tag simple-knowledge-sharing-platform/db:latest <region-code>.ocir.io/<tenancy-namespace>/4qiita-sample/db:latest
$ docker push <region-code>.ocir.io/<tenancy-namespace>4qiita-sample/db:latset
```

以上でリモートレポジトリへのプッシュは完了です。

# マニフェストファイルの作成
次に、クラスターにデプロイするために各リソースのマニフェストファイルを作成します。今回作成するリソースは、
- WebサーバのDeployment
- DBのDeployment
- Webサーバアクセス用のService
- WebサーバがDBにアクセスするためのService

の４つです。
## WebサーバのDeployment
適当なディレクトリに移動して、以下のコマンドでまずマニフェストファイルのひな型を作成します。
```bash
$ kubectl create deploy 4qiita-sample-web --image=<region-code>.ocir.io/<tenancy-namespace>/4qiita-sample/web:latest --replicas=3 --dry-run=client -o yaml >> deploy_web.yaml
```
以下のようなyamlが出力されるはずです。
```yaml:deploy_web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: 4qiita-sample-web
  name: 4qiita-sample-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: 4qiita-sample-web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: 4qiita-sample-web
    spec:
      containers:
      - image: <imageで指定した値>
        name: web
        resources: {}
status: {}
```
このままでも問題ありませんが、不要な部分を削除にしてシンプルにします。
```yaml:編集後のdeploy_web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: 4qiita-sample-web
  name: 4qiita-sample-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: 4qiita-sample-web
  template:
    metadata:
      labels:
        app: 4qiita-sample-web
    spec:
      containers:
      - image: <imageで指定した値>
        name: web
```

## DBのDeployment
同様に、DBもまずひな型を作成します。
```bash
$ kubectl create deploy 4qiita-sample-db --image=<region-code>.ocir.io/<tenancy-namespace>/4qiita-sample/db:latest --replicas=1 --dry-run=client -o yaml >> deploy_db.yaml
```
修正すると以下のようになります。
```yaml:deploy_db.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: 4qiita-sample-db
  name: 4qiita-sample-db
spec:
  replicas: 1
  selector:
    matchLabels:
      app: 4qiita-sample-db
  template:
    metadata:
      labels:
        app: 4qiita-sample-db
    spec:
      containers:
        - image: <imageで指定した値>
          name: db
```

## Webサーバアクセス用のService
同様に、以下のコマンドでひな型を作成します。
```bash
$  kubectl create svc loadbalancer qiita-sample-web --tcp=8080:5000 --dry-run=client -o yaml >> deploy_web_svc.yaml
```
以下のようなマニフェストファイルが出力されます。
```yaml:deploy_web_svc.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: qiita-sample-web
  name: qiita-sample-web
spec:
  ports:
  - name: 8080-5000
    port: 8080
    protocol: TCP
    targetPort: 5000
  selector:
    app: 4qiita-sample-svc
  type: LoadBalancer
status:
  loadBalancer: {}
```
不要な部分を削除します。
```yaml:不要な部分削除後のdeploy_web_svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: qiita-sample-web
  name: qiita-sample-web
spec:
  ports:
  - name: 8080-5000
    port: 8080
    protocol: TCP
    targetPort: 5000
  selector:
    app: 4qiita-sample-svc
  type: LoadBalancer
```
WebサーバのPodにルーティングするので、selectorを`app: 4qiita-sample-web`に変更します。
```yaml:セレクター変更後のdeploy_web_svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: qiita-sample-web
  name: qiita-sample-web
spec:
  ports:
    - name: 8080-5000
      port: 8080
      protocol: TCP
      targetPort: 5000
  selector:
    app: 4qiita-sample-web
  type: LoadBalancer
```



今回、Serviceのタイプとして`LoadBalancer`を指定しています。このタイプでデプロイすると、OCIのロードバランサ―を自動的にプロビジョニングしてくれるのですが、少しだけ追加で設定を書く必要があります。追加の設定は`metadata.annotations`に記述します。
```yaml:annotation追加後のdeploy_web_svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: qiita-sample-web
  name: qiita-sample-web
  annotations:
    oci.oraclecloud.com/load-balancer-type: "lb"
    service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "30"
spec:
  ports:
    - name: 8080-5000
      port: 8080
      protocol: TCP
      targetPort: 5000
  selector:
    app: 4qiita-sample-web
  type: LoadBalancer
```
## DBアクセス用のService
同様に以下のコマンドでひな型を作成します。
```bash
$ kubectl create svc clusterip --tcp=3306:3306 --dry-run=clinet -o yaml >> deploy_db_svc.yaml
```
不要な部分を削除すると以下のようになります。
```yaml:deploy_db_svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: qiita-sample-db
  name: qiita-sample-db
spec:
  ports:
  - name: 3306-3306
    port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: qiita-sample-db
  type: ClusterIP
```
selectorをDBのDeploymentのラベルに変更します。
```yaml:セレクタ変更後のdeploy_db_svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: qiita-sample-db
  name: qiita-sample-db
spec:
  ports:
    - name: 3306-3306
      port: 3306
      protocol: TCP
      targetPort: 3306
  selector:
    app: 4qiita-sample-db
  type: ClusterIP
```

以上で作成完了です。
なお、WebのPodからはこのService名でDB用のPodにアクセスできます。ですので、`qiita-sample-db`という名前でアクセス可能です。

# クラスターにデプロイ
作成したマニフェストファイルを適用して、クラスターにアプリケーションをデプロイします。以下のコマンドで適用できます。
```bash
$ kubectl apply -f deploy_db.yaml
$ kubectl apply -f deploy_web.yaml
$ kubectl apply -f deploy_web_svc.yaml
$ kubectl apply -f deploy_db_svc.yaml
```
正しく作成されたかどうか、確認します。
```bash
$ kubectl get deploy
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
4qiita-sample-db    1/1     1            1           2m9s
4qiita-sample-web   3/3     3            3           111s
```
すべて`READY`になっています。
```bash
$ kubectl get svc
NAME               TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)             AGE
kubernetes         ClusterIP      10.96.0.1      <none>          443/TCP,12250/TCP   3h32m
qiita-sample-db    ClusterIP      10.96.157.65   <none>          3306/TCP            5m49s
qiita-sample-web   LoadBalancer   10.96.71.82    146.56.42.208   8080:32154/TCP      89s
```
作成した二つのService（`qiita-sample-db`, `qiita-sample-web`）があることがわかります。

## アクセスしてみる
作成したロードバランサ―の`<EXTERNAL-IP>:<PORT>`でアクセスします。以下のように無事に画面が表示されれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/fbb0caa6-fa04-33ee-49c6-ac3fcb6ead94.png)

記事の更新が可能か確かめてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/c35f727b-128b-d458-d61e-aec7cdabf1e3.png)
無事に記事の追加ができることが確認できました。

# まとめ
少し長くなりましたが、今回はパブリッククラウドのマネージドKubernetesサービスを利用して、自作のウェブアプリを公開するまでの流れを説明しました。最低限のKubernetesの機能しか使っていないので、まだまだ改善するべき点は多々ありますが、とりあえずデプロイするだけなら比較的簡単に可能であることが分かっていただけたかと思います。

今回はDockerfileの中に環境変数の値を埋め込んでいるため、環境変数の値を変更しようとすると毎回コンテナイメージをビルドする必要があり、不便です。Kubernetes側で環境変数を扱えるようにするため、[次回](https://qiita.com/goroneko/items/4791d3ac97e68c949b5c)はConfigMapについて説明したいと思います。