Corredor

ウェブ、プログラミングの勉強メモ。

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 を作成する

  • OCI における、各種「リソース」を管理する枠となるのが Compartment (コンパートメント)
  • 「リソース」というのは、VM のインスタンスとか、VCN とか、Kubernetes クラスタとか、OCI 内で操作できるモノの総称。
  • そのリソースをどこまで操作できるようにするか、という権限設定が Policy (ポリシー) で、Policy の適用範囲は Group (グループ) 単位などで指定できる。
  • その Group には User (ユーザ) が属する。この User は、OCI 用のアカウントとして発行して普通に人間がログインして利用することもできるが、プログラム内でリソースを操作する際にも、この作成した User を使ったりする。「SYSTEM」ユーザ、みたいな感覚かな。

そんなワケで、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種類のリソースが登場しているので、何のリソースに名前を付けたのか分かるように、リソース種別も名前に含めてしまうと後で分かりやすいかも。

要するにこんな感じ。

  • システム名を my-app とする
  • Compartment : my-app-compartment
  • Group : my-app-managers-group
    • あとで付与する Policy の Verb 部分に沿った Target User の種別として managers を使ってみた。
    • 参考:Policy Reference … Policy Statement の構文説明の中の、Verb 部分の解説。
      • inspectreadauditors あたり、useusers あたり、managemanagersadministrators あたりが分かりやすいかと
  • User : my-app-manager-user
    • 複数ユーザ作る時は「どんなマネージャーなの?」が分かるように工夫…
  • Policy : my-app-managers-policy

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 みたいなエラーが発生してインストールに失敗した場合は、以下のような対処で何とかなるかも。

  • brew install ta-lib を実行してみる
  • ~/.bash_profile に以下の行を追記してやり直す
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ファイルが生成された。

  • ~/.oci/config : oci コマンドが接続する先を示す設定ファイル
  • ~/.oci/oci_api_key.pem : oci コマンドで使用する API 秘密鍵。コレを後で OKE・OCIR 利用時にも流用することにする
  • ~/.oci/oci_api_key_public.pem : API 秘密鍵に対応する公開鍵

作成した 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
  • kubectl create secret docker-registry の後に、任意の Docker Registry Secret 名を指定する。この Docker Secret Registry 名は、この後 Kubernetes の deployment.yaml に記載する
  • --docker-server はホームリージョンに合わせて指定する
  • --docker-usernameTenancy/User の形式で記述する
  • --docker-password は先程 User から発行した Auth Token を指定する
  • --docker-email は、使用しないが必須入力であるため、適当に入力する

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 サーバを構築できた。

Oracle Cloud Infrastructure Study Guide - Infrastructure as Code

Oracle Cloud Infrastructure Study Guide - Infrastructure as Code