OCI ことはじめ : OCIR に Push した Docker イメージを OKE クラスタ上で動かしてブラウザからアクセスするまで

OCI : Oracle Cloud Infrastructure という IaaS を使ってみる。OCI には OCIR : Oracle Cloud Infrastructure Registry と呼ばれるプライベート Docker レジストリと、OKE : Oracle Container Engine for Kubernetes という Kubernetes マネージド・サービスが提供されているので、DockerHub には Push したくないプライベートな Docker イメージを置いておけるし、その Docker イメージを使って Kubernetes クラスタに Pod をデプロイしたりできる。

今回はこの OCI が提供する OKE と OCIR を活用して、最終的にブラウザからアクセスできる Web サーバを立ち上げてみたいと思う。

事前準備が多いので、順を追って説明する。今回は MacOS Mojave 環境で構築した。

目次

Docker のインストール

開発マシンに Docker Desktop をインストールしておく。今回は細かな手順は省略。

ターミナルで $ docker コマンドを実行し、Docker が利用可能な状態にしておく。

Kubernetes のインストール

コチラも細かな手順は省略。以下のサイトより、OS に合う方法でインストールする。

MacOS の場合、Homebrew を利用してインストールできたので、$ brew install kubernetes-cli だけで OK。ターミナルで $ kubectl コマンドが叩ける状態にしておく。

OCI 管理コンソールに移動する

Oracle Cloud My Services ダッシュボードにログインしたら、左メニューから「サービス」→「Compute」と進む。

Compartment・Group・User・Policy を作成する

そんなワケで、OCIR や OKE を使うためのアレコレを作っていく。

  1. Compartment を作成する
    • OCI コンソールの左上ハンバーガーメニューアイコン → Identify → Compartments を選択する
    • 「Create Compartment」ボタンを押下する
    • Name : コンパート名を設定する・あとで変更可能 (命名については後述)
    • Description : 任意・あとで変更可能
    • Tags : 任意・今回は指定しない
    • 入力したら「Create Compartment」ボタンを押下する
  2. OKE・OCIR を使用するユーザ向けの Group を作成する
    • OCI コンソールメニュー → Identify → Groups と移動する
    • 「Create Group」ボタンを押下する
    • Name : グループ名を設定する・あとでの変更は不可
    • Description : 任意・変更不可
    • 入力したら「Submit」ボタンを押下する
  3. OKE・OCIR 操作時に使用する API キー設定および Auth Token 発行用の User を作成する
    • OCI コンソールメニュー → Identify → Users と移動する
    • 「Create User」ボタンを押下する
    • Name : ユーザ名を設定する・変更不可。以降では分かりやすくするため my-app-manager-user と呼ぶことにする。
    • Description : 任意・あとで変更可能
    • 入力したら「Create」ボタンを押下する
  4. 作成した User を先程の Group に所属させる
    • 作成した User (my-app-manager-user) の詳細画面に移動する
    • 左カラム Resources メニュー → Groups を選択する
    • 「Add User to Group」ボタンを押下する
    • Groups : 「Select a Group」セレクトボックスより、先程作成した Group を選択する
    • 「Add」ボタンを押下する
    • 別の操作手順 (どちらでも同じ)
    • Group 詳細画面に移動し、「Add User to Group」ボタンを押下する
    • User : 「Select a User」セレクトボックスより User (my-app-manager-user) を選択し「Add」ボタンを押下する
  5. OKE を使用するための Policy を作成する
    • OCI コンソールメニュー → Identify → Policies と移動する
    • 左カラム Compartment メニュー → 「Pick a compartment」セレクトボックスより、ルート Compartment を選択する
    • 「Create Policy」ボタンを押下する
    • Name : ポリシー名を設定する・変更不可
    • Description : 任意・変更不可
    • Policy Versioning : Keep Policy Current を選択 (初期状態のまま)
    • Policy Statements : 以下のとおりに入力する
      • allow service OKE to manage all-resources in tenancy
    • 入力したら「Create」ボタンを押下する
  6. 作成した Group に所属する User が OCIR を使用するための Policy を作成する
    • 先程と同様の手順で、ルート Compartment を選択した状態で「Create Policy」ボタンを押下する
    • Name : ポリシー名を設定する
    • Description : 任意
    • Policy Versionin : Keep Policy Current を選択 (初期状態のまま)
    • Policy Statements : 以下の要領で入力する
      • allow group 【作成した Group 名】 to manage repos in tenancy
    • 入力したら「Create」ボタンを押下する

命名規則のオススメ

Compartment・Group・User・Policy の名称は、半角英数字とハイフンあたりが使える。大文字も使えるが、個人的なオススメは小文字のみのハイフンケース。この時点で4種類のリソースが登場しているので、何のリソースに名前を付けたのか分かるように、リソース種別も名前に含めてしまうと後で分かりやすいかも。

要するにこんな感じ。

Policy の Statement は、普通の英文に見えるかもしれないが、決まった構文がある。以下を参考にされたし。

OCI CLI のインストール

OKE で構成した kubernetes クラスタの操作には、kubectl コマンドを使うワケだが、その接続に必要な環境情報を得るため、まずは OCI CLI というコマンドラインツールをインストールする。

OCI CLI のインストールに際しては Python3・pip3 が必要になるため、未インストールの場合は Homebrew などでインストールしておく。

# Homebrew で Python3 をインストールする
$ brew install python

# Python3 がインストールされたことを確認する
$ python3 -V
Python 3.7.2

# pip3 も同時にインストールされたことを確認する
$ pip3 -V
pip 18.1 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)

# `python` コマンドで v3 系が使用されるよう PATH を設定する (brew info python で紹介されている)
echo 'PATH="/usr/local/opt/python/libexec/bin:$PATH"' >> ~/.bash_profile

続いて、以下のコマンドで OCI CLI のインストールを開始する。

$ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"
# 色々質問されるので、それぞれ次のように回答して進めていく

===> In what directory would you like to place the install? (leave blank to use '/Users/【ユーザ】/lib/oracle-cli'):  # ★
  # → 空白のまま Enter を押下する。ライブラリが `~/lib/oracle-cli/` 配下にインストールされる

===> In what directory would you like to place the 'oci' executable? (leave blank to use '/Users/【ユーザ】/bin'):  # ★
  # → 空白のまま Enter を押下する。コマンドが `~/bin/` 配下にインストールされる

===> In what directory would you like to place the OCI scripts? (leave blank to use '/Users/【ユーザ】/bin/oci-cli-scripts'): # ★
  # → 空白のまま Enter を押下する。OCI CLI スクリプトが `~/bin/oci-cli-scripts/` 配下にインストールされる

===> Modify profile to update your $PATH and enable shell/tab completion now? (Y/n): Y  # ★
  # → PATH にタブ補完用のスクリプトを追記するかどうか。指示どおり `Y` を入力する

===> Enter a path to an rc file to update (leave blank to use '/Users/【ユーザ】/.bash_profile'):  # ★
  # → タブ補完用の PATH を追記するファイルを問われている。空白のまま Enter を押下し、`~/.bash_profile` に追記させる

ターミナルを再起動 ($ exec -l $SHELL) し、oci コマンドが動作することを確認する。

~/.bash_profile 末尾には以下のような1行が追記されている。コレは、OCI CLI コマンドの Tab 補完用のスクリプト読み込み処理。

[[ -e "/Users/【ユーザ】/lib/oracle-cli/lib/python3.7/site-packages/oci_cli/bin/oci_autocomplete.sh" ]] && source "/Users/【ユーザ】/lib/oracle-cli/lib/python3.7/site-packages/oci_cli/bin/oci_autocomplete.sh"

インストール中、command 'clang' failed with exit status 1 みたいなエラーが発生してインストールに失敗した場合は、以下のような対処で何とかなるかも。

export LDFLAGS="-L/usr/local/opt/openssl/lib"
export CPPFLAGS="-I/usr/local/opt/openssl/include"
export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig"

この他にも、環境によってインストール時にエラーが発生しやすい。python コマンドで Python3、pip コマンドで pip3 が利用できるよう PATH を通してあるかを中心に環境を確認すると良い。

OCI CLI の初期設定

ターミナルで $ oci setup config コマンドを実行し、OCI CLI の初期設定を行う。

途中、OKE・OCIR 使用時の API キーを生成する。

oci コマンドから対象のテナンシーにアクセスできるよう初期設定する。

$ oci setup config
# またいくつか質問されるので答えていく

Enter a location for your config [/Users/【ユーザ】/.oci/config]:  # ★
  # → 空白のまま Enter

Enter a user OCID: ocid1.user.xxxxxxxxxx  # ★
  # → 先程作成した User (my-app-manager-user) の OCID を入力する

Enter a tenancy OCID: ocid1.tenancy.xxxxxxxxxx  # ★
  # → Tenancy の OCID

Enter a region (e.g. eu-frankfurt-1, uk-london-1, us-ashburn-1, us-phoenix-1): us-ashburn-1  # ★
  # → Tenancy のホームリージョンを指定する。ココでは us-ashburn-1 を指定したテイ

Do you want to generate a new RSA key pair? (If you decline you will be asked to supply the path to an existing key.) [Y/n]: Y  # ★
  # → API キー (秘密鍵) を生成するため `Y` を入力する

Enter a directory for your keys to be created [/Users/【ユーザ】/.oci]:  # ★
  # → 空白のまま Enter・生成先ディレクトリパスの指定

Enter a name for your key [oci_api_key]:  # ★
  # → 空白のまま Enter・生成する API キーのファイル名
Public key written to: /Users/【ユーザ】/.oci/oci_api_key_public.pem

Enter a passphrase for your private key (empty for no passphrase):  # ★
  # → 空白のまま Enter・API キーにパスフレーズを付けられるが、今回は付けない
Private key written to: /Users/【ユーザ】/.oci/oci_api_key.pem
Fingerprint: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
Config written to /Users/【ユーザ】/.oci/config

以上の初期設定で、以下の3ファイルが生成された。

作成した User に API キーの公開鍵を登録する

先程作成した my-app-manager-user に、~/.oci/oci_api_key_public.pem ファイルに書き出された API 公開鍵を紐付ける。

  1. User の詳細画面に移動する
  2. 左カラム Resources メニュー → API Keys を選択する
  3. 「Add Public Key」ボタンを押下する
  4. Public Key 欄に、前述の公開鍵 oci_api_key_public.pem の内容をコピー & ペーストする
    • BEGIN PUBLIC KEY から END PUBLIC KEY の記載がある行まで全てを貼り付ける
  5. 貼り付けたら「Add」ボタンを押下する
  6. 登録した API キーの「Fingerprint」を確認する。oci setup config コマンドの最後や、~/.oci/config ファイルの中に記載の Fingerprint と一致していることが確認できる

OKE Cluster を作成する

いよいよ Kubernetes クラスタを作成する。

  1. OCI コンソールメニュー → Developer Services → Container Clusters (OKE) と選択する
  2. 左カラム Compartment メニュー → セレクトボックスより作成した Compartment を選択する
  3. 「Create Cluster」ボタンを押下する
    • Name : 任意で決める
    • Kubernetes Version : v1.11.5 (初期状態のまま)
    • ラジオボタン : Quick Create を選択 (初期状態のまま) → コレによりインスタンスや VCN などを自動生成してくれる
    • その他 : 初期状態のまま
  4. 入力したら「Create」ボタンを押下する
  5. VCN や Subnet などが自動生成されるので、「Close」ボタンを押下する
  6. Cluster 詳細画面に移動し、「Cluster Status」が「Creating」から「Active」になるまで10分程度待つ

Kubernetes 環境を設定する

先程作成した OKE クラスタに、kubectl コマンドでアクセスできるようにするための設定。

  1. 作成した Cluster の詳細画面に移動する
  2. 「Access Kubeconfig」ボタンを押下する
  3. 表示された次のようなスクリプトをコピーする
mkdir -p $HOME/.kube
oci ce cluster create-kubeconfig --cluster-id ocid1.cluster.xxxxxxxxxx --file $HOME/.kube/config --region us-ashburn-1

このスクリプトをターミナルで実行すると、~/.kube/config ファイルが生成され、kubectl コマンドで OKE クラスタに接続できるようになる。この KubeConfig と呼ばれるファイルの中身は YAML 形式の設定ファイルだ。

次のコマンドで、kubectl を用いて実際に OKE Cluster にアクセスできるか確認する

$ kubectl get all
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   11m

Docker イメージを OCIR へ Push するための準備

OKE クラスタが構築でき、コマンドラインから操作できるようになったので、次は OCIR (プライベートレジストリ) にオリジナルの Docker イメージをプッシュしておこう。あとでコレを OKE クラスタにデプロイする。

  1. 作成した User my-app-manager-user より、OCIR に Docker イメージを Push するための Auth Token を発行する
    • OCI コンソールにて、User の詳細画面に移動する
    • 左カラム Resources メニュー → Auth Tokens を選択する
    • 「Generate Token」ボタンを押下する
    • Description : 任意
    • 入力したら「Generate」ボタンを押下する
    • 次画面の「Generated Token」欄に表示されているトークン文字列をコピーする。この画面を閉じると二度と確認できないため、必ずコピーして控えておく。
    • トークン文字列がコピーできたら「Close」ボタンを押下する
  2. Docker コマンドで OCIR にログインする
    • 以下のように、echo コマンドに Auth Token を指定し、docker login コマンドの --password-stdin オプションにパイプで渡す
    • docker login コマンドでログインするドメイン名は、リージョンにあわせて指定する
    • -u オプションで指定するユーザ名は、Tenancy/User の形式で指定する
$ echo 'XXXxxx(XXXX#00XXXXXX' | docker login iad.ocir.io -u my-app-tenancy/my-app-manager-user --password-stdin
Login Succeeded

Docker イメージを OCIR へ Push する

任意の Dockerfile を用意して、docker build コマンドで Docker イメージを作成しておこう。今回は詳しい説明は省略するが、とりあえず何か Web サーバっぽいモノが動いている想定。

コレを OCIR にプッシュするには、Push 用のタグを付けてから Push する必要がある。

# Push 用のタグを付与する : 規則は 【リージョン別ドメイン】/【Tenancy】/【任意のリポジトリ名】/【任意の Docker イメージ名】
$ docker image tag my-app-docker-image:v1 iad.ocir.io/my-app-tenancy/my-app-repository/my-app-docker-image:v1

# タグ付けした Docker イメージを Push する
$ docker push iad.ocir.io/my-app-tenancy/my-app-repository/my-app-docker-image:v1

Push できたら、OCI コンソールより、OCIR に Docker イメージが Push されているか確認してみよう。

OCI コンソールメニュー → Developer Services → Registry (OCIR) に移動し、一覧にリポジトリとイメージが存在していれば OK。

OKE Cluster で OCIR 上の Docker イメージを使えるように設定する

OKE クラスタで、OCIR 上の Docker イメージを使えるようにするには、Docker Registry Secret というクレデンシャル情報 (秘密情報) を Kubernetes に登録する必要がある。秘密情報は開発マシンに保存されるのではなく、Kubernetes クラスタ内に保持される。

ではその登録手順。ターミナルで、以下のようにコマンドを実行する。

$ kubectl create secret docker-registry my-app-ocir-secret \
  --docker-server=iad.ocir.io \
  --docker-username='my-app-tenancy/my-app-manager-user' \
  --docker-password='XXXxxx(XXXX#00XXXXXX' \
  --docker-email='example@example.com'

secret/my-app-ocir-secret created

Secret が生成できたら、以下のコマンドで確認できる。

$ kubectl get secret
NAME                            TYPE                                  DATA   AGE
default-token-hrv9f             kubernetes.io/service-account-token   3      41m
my-app-ocir-secret              kubernetes.io/dockerconfigjson        1      5s

OKE Cluster に Docker イメージをデプロイする

Kubernetes に Docker イメージを冗長構成でデプロイするための deployment.yaml を作成する。

apiVersion: apps/v1  # Kubernetes のバージョンに依存して決まる
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  selector:  # Label Selector : 指定の labels と完全一致した Pods が紐付く
    matchLabels:  # 完全一致なので、spec.template.metadata.labels: セクションと全く同じ内容を書く
      app: my-app
  replicas: 2  # レプリカ数
  template:  # Pod Template
    metadata:
      labels:
        app: my-app  # Pod 名。Key・Value 形式で、Key は何でも良い
    spec:
      containers:
      - name: my-app
        image: iad.ocir.io/my-tenancy/my-app-repository/my-app-docker-image:v1  # 使用する Docker イメージ名
        imagePullPolicy: Always
        ports:
        - containerPort: 8080  # コンテナが公開したいポート・Dockerfile の EXPOSE 命令に書いたポートと合わせておく
      imagePullSecrets:
      - name: my-app-ocir-secret  # Docker Registry Secret 名を記述する

deployment.yaml の書き方は Kubernetes の使い方の中で調べてみてほしい。ココでは、Docker イメージ内で動作する Web サーバが 8080 ポートを使っている前提で containerPort を指定している。Node.js Express サーバなんかだと、80番のようないわゆる「Well-known Port」を使用するにはひと手間必要だったりするので、こんな風にした次第。

そしたらこのファイルを適用して、Pod をデプロイさせる。

$ kubectl apply -f deployment.yaml
deployment.apps/my-app-deployment created

# 少し待って Pod が生成されていることを確認する
$ kubectl get pod
NAME                                        READY   STATUS    RESTARTS   AGE
my-app-deployment-xxxxxxxxxx-yyyyy   1/1     Running   0          37s
my-app-deployment-xxxxxxxxxx-zzzzz   1/1     Running   0          1m

# 各 Pod の直近の標準出力を確認して、起動しているか見てみる
$ kubectl logs -lapp=my-app

コレで、Kubernetes クラスタ内で Pod が稼動し始めた。

Load Balancer を作成する

このままでは稼動している Pod に外部からアクセスできないので、Load Balancer を作成する。

以下のような service.yaml を用意する。

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  - name: httpwell
    port: 80
    targetPort: 8080

先程も書いたとおり、Docker イメージが実際に使用するのは 8080 ポートなので、8080 ポートで直接アクセスできる口も作ってあって、さらに80番ポートでアクセスした時に Pod には 8080 ポートで転送されるような name: httpwell 指定も入れている。あんまりやたらとポートを開けておかない方が安全なので、本番利用時は80番 (HTTP) と443番 (HTTPS) くらいに絞った方が良いかと。

さて、コレを次のようなコマンドで実行し、Load Balancer を生成させる。

$ kubectl apply -f service.yaml
service/my-app-service created

# Load Balancer 作成中。パブリック IP (`EXTERNAL-IP`) の発行を数分待つ
$ kubectl get service
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                       AGE
kubernetes              ClusterIP      10.96.0.1      <none>        443/TCP                       49m
my-app-service          LoadBalancer   10.96.200.00   <pending>     8080:31779/TCP,80:32202/TCP   5s

# パブリック IP が発行された
$ kubectl get service
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                       AGE
kubernetes              ClusterIP      10.96.0.1      <none>           443/TCP                       50m
my-app-service          LoadBalancer   10.96.200.00   128.200.190.10   8080:31779/TCP,80:32202/TCP   57s

ココで確認できる EXTERNAL-IP が、実際に外部から接続できるパブリック IP アドレスである。というワケで、http://128.200.190.10/ といった URL でブラウザからアクセスしてみよう。実際に疎通できるようになっているはずだ。

ココで生成した Load Balancer や Public IP は、OCI コンソールメニュー → Networking → Load Balancers および Public IPs で確認できる。kubectlservice を変更しても上手く反映されていないように見える場合は、OCI コンソールで状況を確認してみよう。

以上

初期設定が物凄く長かった…。Terraform とか使ったらもう少しスクリプト化できるのかな…。

ひとまずこんな流れで、OCI を初めて触った人間が、OKE と OCIR を活用して、ブラウザからアクセスできる Web サーバを構築できた。