Docker
kubernetes

Kubernetes上でアプリケーションデプロイをサクッと試してみる(コマンドあり)


背景

最近アプリケーション実行環境としてコンテナ仮想環境は当たり前になりつつあると感じています。個人的にもサクッと新しい技術を(OSSなりツールなり)試したりするのにもローカルを汚さず素早く環境構築を行えるので、これがないと逆に効率が悪くなるなと思います。


そんな風潮の中で社内でもKubernetesによるアプリケーション環境構築や運用保守がなされており、(コンテナ仮想化ソフトはもう当たり前として)社内外でk8sの存在感が絶大です。


僕自身はアプリケーションエンジニア(フロントエンド/バックエンドどちらも見る)ではありますが、k8sの知見を増やしたいと思い「Kubernetes実践入門」という書籍を参考にローカルにk8sでアプリケーションデプロイまで構築してみました。目次は書籍の章立てとはあまり関係ないですが特に重要と思われる部分だけピックアップし順序だてていますので試しに触って動かしてみたい方には参考になるかなと思います。


目次


前提


  • MacbookPro, MacOS Mojave(version=10.14.3)

  • VirtualBox(version=5.2.20 r125813)

  • Homebrew(version=2.1.0)

  • k8sのオブジェクトについて概ね理解している。

k8sには内部構造を抽象化しk8s内で利用しやすくするために独自のオブジェクトの概念があります。僕自身は以前k8sについて調べたことがあったので(知見として頭に入れただけで使えるようにはなってませんでした)あえてそこには触れませんが、記事内では固有名詞で頻出しますのでわからない方は以下の記事などで先に理解しておくことをお勧めします。(まぁそれを理解するのがもっとも難しいのですが。。。)

【参考記事】


minikubeでクラスタを構築してみる

ローカル環境でk8sを動かすためにminikubeを利用します。k8sの公式で提供しているので操作についてほとんど差異はないはずです。


インストール方法

# minikubeをインストール

$ brew cask install minikube

# kubernetes-cliをインストール
$ brew install kubernetes-cli


クラスタ構築

初回実行時はVMイメージのダウンロードから始まるので時間がかかるようです。

(実は、一度失敗してしまったのでこちらの記事を参考に再実行しました。)

$ minikube start

😄 minikube v1.0.0 on darwin (amd64)
🤹 Downloading Kubernetes v1.14.0 images in the background ...
🔥 Creating virtualbox VM (CPUs=2, Memory=2048MB, Disk=20000MB) ...
💿 Downloading Minikube ISO ...
142.88 MB / 142.88 MB [============================================] 100.00% 0s
📶 "minikube" IP address is 192.168.99.100
🐳 Configuring Docker as the container runtime ...
🐳 Version of container runtime is 18.06.2-ce
⌛ Waiting for image downloads to complete ...
✨ Preparing Kubernetes environment ...
💾 Downloading kubeadm v1.14.0
💾 Downloading kubelet v1.14.0
🚜 Pulling images required by Kubernetes v1.14.0 ...
🚀 Launching Kubernetes v1.14.0 using kubeadm ...
⌛ Waiting for pods: apiserver proxy etcd scheduler controller dns
🔑 Configuring cluster permissions ...
🤔 Verifying component health .....
💗 kubectl is now configured to use "minikube"
🏄 Done! Thank you for using minikube!

クラスタの確認をしてみる

$ kubectl get nodes

NAME STATUS ROLES AGE VERSION
minikube Ready master 2m23s v1.14.0

minikubeという1台のNodeが稼働しています。


クラスタの停止、削除する

minikubeはローカルでクラスタを実行しているため実行中ノードのリソースを占有するようです。よって利用しない場合はクラスを終了しておいた方がいいでしょう。

クラスタを停止して再度minikube startした際は、停止前の状態のクラスタが起動します。削除した場合は初期状態のクラスタが起動されますのでリセットしたい場合に利用できます。

# クラスタを停止する

$ minikube stop

# クラスタを削除する
$ minikube delete


アドオンを管理する

minikubeにはよく使われる機能を拡張機能として利用できるアドオンがあります。

# アドオンの一覧を列挙する

$ minikube addons list
- addon-manager: enabled
- dashboard: disabled
- default-storageclass: enabled
- efk: disabled
- freshpod: disabled
- gvisor: disabled
- heapster: disabled
- ingress: disabled
- logviewer: disabled
- metrics-server: disabled
- nvidia-driver-installer: disabled
- nvidia-gpu-device-plugin: disabled
- registry: disabled
- registry-creds: disabled
- storage-provisioner: enabled
- storage-provisioner-gluster: disabled

デフォルトでいくつかのアドオンが有効になっています。アドオンを有効/無効にする場合は以下を実行します。

# アドオンを有効にする

$ minikube addons enable {addon-name}

# アドオンを無効にする
$ minikube addons disable {addon-name}

たとえば、クラスタのWEB管理画面であるdashboardminikube dashboardを実行することでブラウザで表示されます。

k8s dashboardの画面

k8s dashboardの画面.png


k8s上にアプリケーションをデプロイしてみる

サンプルアプリにはMattermostというSlackライクなチャットアプリケーションを利用します。


簡易的にデプロイする

ここからは、k8s上へのアプリデプロイを素早くやってみたい、デプロイされた後の動きをdashboardで確認してみたいと思い、簡易的なデプロイ手順を記載しています。本番環境では利用をおすすめしませんのでより本番に近い方法は こちら に進んでください。


アプリケーションをデプロイする - kubectl create deployment

k8上にアプリケーションをデプロイする簡単な方法はkubectl create deploymentを実行することです。アプリケーションの識別名とコンテナイメージの指定は必須です。

createdが返ればDeploymentオブジェクトが作成され、デプロイが開始したことを意味します。

$ kubectl create deployment mattermost-preview --image k8spracticalguide/mattermost-preview:4.10.2

deployment.apps/mattermost-preview created

デプロイが開始されれば、kubectl get deploymentを実行してDeploymentの状態を確認しましょう。何度か実行しているうちにAVAILABLEが1に変わると思います。

$ kubectl get deployment mattermost-preview

NAME READY UP-TO-DATE AVAILABLE AGE
mattermost-preview 1/1 1 1 69s


アプリケーションを公開する - kubectl expose

デプロイが完了しても、クラスタ外からはアクセスできません。クラスタ外からアクセスできるようにするにはkubectl exposeコマンドを--typeオプションにNodePortを指定して実行する必要があります。

現段階では「Nodeportはクラスタ外からNodeへアクセスするためのポート」という理解で良いと思います。。。

$ kubectl expose --type NodePort --port 8065 deployment mattermost-preview

service/mattermost-preview exposed

以上でクラスタ外からアクセスできる状態になったので、ブラウザから確認してみます。ブラウザからアクセスするには、アプリケーションのアドレスとポート番号を調べ、ブラウザを開き・・という少々面倒ですので、それらを一括で実行してくれるminikube serviceコマンドを使います。

$ minikube service mattermost-preview

🎉 Opening kubernetes service default/mattermost-preview in default browser...

mattermostの画面

mattermostの画面.png


アプリケーションのマニフェストを作成してデプロイする

上記ではkubectl create deploymentを利用して簡易的にDeploymentを作成しましたが、これではファイルとして履歴が残らないため、レビューしにくく、再利用ができず、問題発生時に原因究明しにくいなど本番環境で利用するには問題が起きそうです。

そのため本番利用を見据えては、Deploymentなどの宣言的設定をyamlやjsonなどで記述しバージョン管理を行うのが一般的かと思います。それらの設定ファイルをマニフェストと呼びます。

ここからアプリコンテナは、mattermostのお試し用(mattermost-preview)ではなく、実利用版(mattermost)を利用していきます。


Deploymentマニフェストを作成する

いきなり0から作るのは難しいため、--dry-runオプションで雛形を作成し、修正しながら作成していくこととします。


--dry-runオプションは雛形作成のためのオプションではなく、実際にkubectrlがAPIを叩く前のリクエスト内容を取得するためのコマンドです。外部出力の--outputオプションと併用して雛形作成を行います。

# mattermost(アプリ)の雛形を作成

$ kubectl create deployment mattermost --image k8spracticalguide/mattermost:4.10.2 -o yaml --dry-run > mattermost-deploy.yaml

# mattermost-db(db)の雛形を作成
$ kubectl create deployment mattermost-db --image k8spracticalguide/mysql:5.7.22 -o yaml --dry-run > mattermost-db-deploy.yaml


DeploymetnマニュフェストのPodテンプレートに環境変数を追加する

mattermostのイメージはDB用の設定値を環境変数で定義できるようになっています。

これらはデプロイする環境や構成によって変更する可能性があるため、コンテナイメージに直接持つのではなく、Deploymentオブジェクト作成時に設定するようにしたいと思います。


ConfigMapを使って設定値を管理、読み込みする

複数のPodテンプレート内に共通の設定値を与える場合、ConfigMapというオブジェクトに設定値を集約して設定値のばらつきによる不具合を抑えることができます。

# ConfigMapのマニフェストを作成する

$ kubectl create cm common-env -o yaml --dry-run \
--from-literal MYSQL_USER=mm_user \
--from-literal MYSQL_PASSWORD=P@ssw0rd \
--from-literal MYSQL_DATABASE=mattermost > cm.yaml

Deploymentの方は以下のように修正する。


mattermost-deploy.yaml

(1~18行目は略)

spec:
containers:
- image: k8spracticalguide/mattermost:4.10.2
name: mattermost
+ env:
+ - name: MM_USERNAME
+ valueFrom: # <- valueではなくvalueFromを使う
+ configMapKeyRef:
+ name: common-env # <- ConfigMap名を指定する
+ key: MYSQL_USER # <- dataマップのキーを指定する
+ - name: MM_PASSWORD
+ valueFrom:
+ configMapKeyRef:
+ name: common-env
+ key: MYSQL_PASSWORD
+ - name: DB_NAME
+ valueFrom:
+ configMapKeyRef:
+ name: common-env
+ key: MYSQL_DATABASE
+ - name: DB_HOST
+ value: mattermost-db
resources: {}
(略)


mattermost-db-deploy.yaml

(1~18行目は略)

spec:
containers:
- image: k8spracticalguide/mysql:5.7.22
name: mysql
+ env:
+ - name: MYSQL_ROOT_PASSWORD
+ value: rootpassword
+ envFrom: # <- envではなくenvFromに置き換える
+ - configMapRef:
+ name: common-env # <- ConfigMap名を指定する
resources: {}
(略)


別ファイルで設定値を管理、読み込みする

さらに、設定が膨大に増えると環境変数として渡すのではなく設定ファイルとしてまとめて設定値を渡す方が管理しやすくなることもあります。

# mattermostの設定ファイルをダウンロードしておく

$ curl -L -O https://raw.githubusercontent.com/kubernetes-practical-guide/examples/master/ch3.4.2.2/config.json

# ファイルからConfigMapのマニフェストを作成する
$ kubectl create cm mm-config-file -o yaml --dry-run \
--from-file ./config.json > cm-file.yaml

# ダウンロードした設定ファイルは不要なので削除する
$ rm ./config.json

Deploymentの方は以下のように修正する。


mattermost-deploy.yaml

(1~38行目は略)

- name: DB_HOST
value: mattermost-db
resources: {}
+ volumeMounts:
+ - name: cm-volume # <- マウントするVolume名を指定(a)
+ mountPath: /mm/config # <- コンテナ内のマウントポイント
+ volumes:
+ - name: cm-volume # <- (a)で指定するVolume名を定義
+ configMap:
+ name: mm-config-file
+ items: # <- itemsを省略するとConfigMap内の全データがマウントされる
+ - key: config.json # <- ファイルとしてマウントするデータのキーを指定
+ path: config.json # <- コンテナ内でのファイル名を指定
(略)


Serviceマニフェストを作成する

各PodはデプロイされるとIPアドレスが一時的に付与されますが、Podを上げ下げするたびにIPアドレスが変更され書き換えが必要になります。

その無駄を回避するため、PodのIPアドレスを仮想IPに束ね内部DNSで名前解決するServieオブジェクトが存在しています。


クラスタ内部のためのServiceを作成する

$ kubectl create service clusterip mattermost-db --tcp 3306 -o yaml --dry-run > mattermost-db-service.yaml

仮想IPに来たアクセスをどのPodへ振り分けするかはservie.yamlのspec.selectorで指定します。


mattermost-db-service.yaml

apiVersion: v1

kind: Service
metadata:
creationTimestamp: null
labels:
app: mattermost-db
name: mattermost-db
spec:
ports:
- name: "3306"
port: 3306
protocol: TCP
targetPort: 3306
selector: # <- リクエストの振り分け先を指定するlabelセレクタ
app: mattermost-db # <- DeploymentのPodテンプレートのlabelと合わせる必要がある
type: ClusterIP
status:
loadBalancer: {}


クラスタ外のための(公開用)Serviceを作成する

# mattermostのDeploymentにServiceを付与するので事前に立ち上げておく

$ kubectl apply -f .

# クラスタ外のための(公開用)Serviceを作成する
$ kubectl expose --type NodePort --port 8065 deploy mattermost -o yaml --dry-run > mattermost-service.yaml

※ NodePortを使えば手軽に外部公開できるようになりますが、直接Serviceを晒す事になったり、アクセスするNodeをどう選択するか問題になりがちです。いくらマニフェスト化して管理しやすくしてもまだ本番利用には不向きだと思いました。開発環境をサクッと構築したい時には便利です。

よって本番では、外部のロードバランサを使って公開するか、Ingressというクラスタ内部のロードバランサを使って公開する方法が有用ですが、今回は構築は実施しません。

(両者の違いについてはこちらが参考になりました。)


マニフェストを適用する

上記の通り操作してくるとマニフェストファイル構成は以下のようになっているはず。

┌ cm-file.yaml  

├ cm.yaml
├ mattermost-db-deploy.yaml
├ mattermost-db-service.yaml
├ mattermost-deploy.yaml
└ mattermost-service.yaml

マニフェストファイルを適用して各種k8sオブジェクトを作成する

$ kubectl apply -f .

ブラウザからアクセスできることを確認するためにNodeのIP、Nodeに割り当てられたポート番号を調べます。

# NodeのIPアドレスを調べる

$ minikube ip
192.168.99.100

# NodePortに割り当てられたのポート番号を調べる
$ kubectl get svc mattermost
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mattermost NodePort 10.102.242.69 <none> 8065:31837/TCP 3m50s

PORT(s)のカラムをみると、2つのポート番号が表示されていますが、{アプリが待ち受けしているポート}:{Nodeに割り当てられたポート}を表しています。

よってhttp://192.168.99.100:31837でブラウザからアクセスできるはずです。

今回は以上です。

サクッと利用してみたいのであればk8sのマネージドサービスも一般的かもしれませんが、利便性の反面、内部の仕組みが隠蔽されて仕組みを理解する題材には向いていないかなと思いましてローカルにminikubeでk8s環境を立ててみました。今回の内容はまだまだ入り口かとは思いますが、実際にアプリケーションをデプロイすることを実践してみてk8sの足がかりになったかなと思います。