初心者が流れで学ぶDocker/Kubernetes超入門

March 08, 2020

はじめに

世はエンジニア戦国時代。Docker くらい一般常識。Docker 使えないなんてエンジニアを名乗れない。そんな時代です。(ほんとか?)

この記事を書き始めた時の僕の Docker 戦闘力は「Docker 公式チュートリアルを眺めただけ」です。 なので逆に言えば Docker 公式チュートリアルをやっただけの方にも理解できるかと思います。

(ちなみに Kubernetes 戦闘力は「なんで Kubernetes を k8s って言うのかだけ知ってる」です。)

この記事はそんな僕が「Docker/Kubernetes ちょっと分かる」になるまでにやったことを後から追えるようにズラっと書き連ねたものになります。

使用するのは僕の大好きな言語 Elixir とその Web フレームワーク Phoenix です。が、この記事でどの言語を用いているかは重要ではありません。 (記事内で Elixir のコードはほぼ触らないですし)

また、Rails がわかる方は以下の記事で Rails と Phoenix のコマンドを対応させて説明しているのでチラ見すると Phoenix で何をしようとしているか理解できるかと思います。 Rails 経験者に贈る Phoenix 入門

何か訂正や補足、アドバイスなどありましたら、是非是非コメントかTwitterまでお願いします!🙇‍♂️

この記事で扱う内容

  • Web アプリケーションを扱える環境をDockerfileで作成する
  • docker-composeを使って Web アプリケーション(+DB)を動かす
  • 作成した image をdockerhubに上げる
  • Kubernetes(minikube)を使って Web アプリケーション(+DB)を動かす

Dockerfile の作成

では早速 Dockerfile を作成していきます

Dockerfile ではこれから作成するコンテナで何をしようとしているかを定義します。

以下の公式リファレンスが参考になります。 Dockerfile リファレンス

FROM elixir:1.10.2

RUN curl -sL https://deb.nodesource.com/setup_12.x | bash

RUN apt-get install -y nodejs

RUN npm install [email protected] -g

RUN mix local.hex --force

RUN mix archive.install hex phx_new 1.4.12 --force

RUN mix local.rebar --force

WORKDIR /app

この Dockerfile が何をしようとしているか

初心者なりに一行ずつ説明してみます。

FROM elixir:1.10.2

親イメージを選択します。 イメージって何?という方は以下の公式チュートリアルの説明がわかりやすいです Part 1:概要説明とセットアップ | コンテナの概要を説明

この親イメージは Elixir 公式の image です。

こういった公式で出ているイメージなどから自分の目的に即した環境が作れるように Dockerfile を記述していって、カスタムしていく訳です。 (今回だと自分の目的=Phoenix を動かせる環境となります)


RUN curl -sL https://deb.nodesource.com/setup_12.x | bash

RUN apt-get install -y nodejs

RUN npm install [email protected] -g

nodejs が必要なのでインストールしています。

ちなみにはじめはこの部分を以下のように記述していたのですが、(nodejs に npm は同梱のはず) こうするとその後 bash: npm: command not found が出てしまいます。

以下のページを参考に上のコードに落ち着きました。 Docker で php コンテナとかに npm をインストールするときのメモ

RUN apt-get update \
    && apt-get install -y nodejs

RUN mix local.hex --force

RUN mix archive.install hex phx_new 1.4.12 --force

RUN mix local.rebar --force

hex という Elixir のパッケージ管理ツールをインストールします。 (Ruby でいう rubygems) ここで --force がついてるのは以下のエラーが出るためです

Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn] ** (Mix) Could not find an SCM for dependency :phx_new from Mix.Local.Installer.MixProject

途中で yes と答えなければいけない部分があるのですが、それを --force をつけることで無視してインストールできます。

postgres はどうすんの?

はい、先ほどの Dockerfile では Elixir(Phoenix)の環境しか整っていません。 postgres のコンテナも作らなければいけないです。

しかし

  • postgres のコンテナと Phoenix のコンテナ間の通信はどうするの?
  • コンテナ間通信を頑張って設定したとしても毎回それを設定するの?
  • 毎回 postgres のコンテナ、Phoenix のコンテナを両方立てるのめんどくせえ

という問題たちが出てきます。 これらを解決してくれるのがdocker-composeです

※ちなみに docker-compose を使わないコンテナ間通信は以下のページを参考にすればできそうです。 https://kitsune.blog/docker-network#i

「いやいや同じコンテナに DB も突っ込めばええやん!」について

そうなるかもですが、コンテナを分けることにはちゃんと理由があります。 この後出てくる docker-composeKubernetes ではアクセス分散のために複数のコンテナで Web サーバーを動かすことができます。

同じコンテナに DB も一緒に入れてしまうと、この際に DB もたくさんできてしまい、どのコンテナに接続されるかで DB の中身が変わってしまうと言う事態が起こります。 これを防ぐために DB と Web でコンテナを分けて Web のコンテナを増やしても同じ DB を参照するように設定すべきな訳です

docker-compose を使用する

docker-compose を使用するために docker-compose.yml を作成します。 docker-compose.ymlには docker のコンテナ達やネットワークについてあるべき姿を記述します。

すると docker-compose がそれを元に良しなに設定してくれるわけです。 以下のように作成します。

version: "3" #docker-composeのバージョン指定
services: #ここより下でserviceを定義
  web:
    build: . #使用するDockerfileの場所
    ports: #portをバインド
      - "4000:4000"
    volumes: #hostの./を/appとして共有
      - .:/app
    command: mix phx.server #サーバー起動のためのコマンド
    depends_on:
      - db #webの開始前にdbを起動

  db:
    image: postgres #使用するimage
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_HOST=db

以下の公式リファレンスがすごく参考になります。 Compose ファイル・リファレンス

docker-compose.yml に定義した commandportsCMDEXPOSE として Dockerfile で定義することもできます。

これで Docker で Elixir/Phoenix の環境を使用する準備ができました。

※volumes に関してはファイルを共有できるという面と、コンテナの外にファイルを安全に置いておけるという面もあります。詳しくは Kubernetes の章で出てきます。

適当なサンプルアプリを作ってみる

テストもかねてサンプルアプリを作ってみます。(アプリ名は dododo にしました)

$ docker-compose run web mix phx.new . --app dododo
Creating network "docker-elixir_default" with the default driver
Creating docker-elixir_db_1 ... done
The directory /app already exists. Are you sure you want to continue? [Yn] y
* creating config/config.exs
* creating config/dev.exs
* creating config/prod.exs
* creating config/prod.secret.exs
* creating config/test.exs
* creating lib/dododo/application.ex
* creating lib/dododo.ex
* creating lib/dododo_web/channels/user_socket.ex
* creating lib/dododo_web/views/error_helpers.ex
* creating lib/dododo_web/views/error_view.ex
* creating lib/dododo_web/endpoint.ex
* creating lib/dododo_web/router.ex
* creating lib/dododo_web.ex
* creating mix.exs
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating test/support/channel_case.ex
* creating test/support/conn_case.ex
* creating test/test_helper.exs
* creating test/dododo_web/views/error_view_test.exs
* creating lib/dododo/repo.ex
* creating priv/repo/migrations/.formatter.exs
* creating priv/repo/seeds.exs
* creating test/support/data_case.ex
* creating lib/dododo_web/controllers/page_controller.ex
* creating lib/dododo_web/templates/layout/app.html.eex
* creating lib/dododo_web/templates/page/index.html.eex
* creating lib/dododo_web/views/layout_view.ex
* creating lib/dododo_web/views/page_view.ex
* creating test/dododo_web/controllers/page_controller_test.exs
* creating test/dododo_web/views/layout_view_test.exs
* creating test/dododo_web/views/page_view_test.exs
* creating lib/dododo_web/gettext.ex
* creating priv/gettext/en/LC_MESSAGES/errors.po
* creating priv/gettext/errors.pot
* creating assets/webpack.config.js
* creating assets/.babelrc
* creating assets/js/app.js
* creating assets/js/socket.js
* creating assets/package.json
* creating assets/css/app.css
* creating assets/static/favicon.ico
* creating assets/css/phoenix.css
* creating assets/static/images/phoenix.png
* creating assets/static/robots.txt

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running cd assets && npm install && node node_modules/webpack/bin/webpack.js --mode development
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd app

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

また、しっかりホストとのファイル共有もできていることがわかります。

$ ls
Dockerfile		_build			config			docker-compose.yml	mix.exs			priv
README.md		assets			deps			lib			mix.lock		test

config/dev.exs の微修正

config/dev.exsは dev 環境の設定ファイルです。 データベースのホスト名を db に変更しておきます。

# Configure your database
config :dododo, Dododo.Repo,
  username: "postgres",
  password: "postgres",
  database: "dododo_dev",
  hostname: "db",          #fix
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

DB の作成

$ docker-compose run web mix ecto.create
Starting docker-elixir_db_1 ... done
(省略)
Generated dododo app
The database for Dododo.Repo has been created

うまく作成できました。 これで DB との連携もうまくいっている事がわかります。

サンプルアプリを立ち上げてみる

$ docker-compose up

以下のように表示されれば成功です。

スクリーンショット 2020-03-07 16.43.47.png

dockerhub にあげる

image を確認して tag をつける

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker-elixir_web   latest              a9ff6e7b157f        29 minutes ago      1.37GB
<none>              <none>              507e3f91e80f        55 minutes ago      1.28GB
dododo_web          latest              d7724891c88c        4 hours ago         1.27GB
elixir              1.10.2              d6641893fb96        12 hours ago        1.23GB
postgres            latest              73119b8892f9        2 days ago          314MB

$ docker tag a9ff6e7b157f sanposhiho/phoenix:latest

dockerhub にログイン

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: sanposhiho
Password:
Login Succeeded

以下のリンクから適当に Create Repository します。 https://hub.docker.com/repository/create スクリーンショット 2020-03-07 20.25.28.png

作った Repository に push します。

$ docker push  sanposhiho/phoenix

dockerhub にあげると何ができるのか

dockerhub にあげる事で Dockerfile が必要なくなります。

すなわちdocker-compose.yml さえあれば先ほどの環境が作成できるわけです。

docker-compose.yml を修正

Dockerfile を使用しない形に docker-compose.yml を修正します。

version: "3"
services:
  web:
    image: sanposhiho/phoenix  #先ほど作成したimage
    ports:
      - '4000:4000'
    volumes:
      - .:/app
    command: mix phx.server
    depends_on:
      - db

  db:
    image: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_HOST=db

変更箇所は web の image の部分です。 Dockerfile を参照していたのを先ほど作成した image を指定しました。

これにより sanposhiho/phoenix をローカルで削除してから docker-compose up しても

$ docker-compose up
Creating network "docker-elixir_default" with the default driver
Pulling db (postgres:)...
latest: Pulling from library/postgres
68ced04f60ab: Pull complete
59f4081d08e6: Pull complete
74fc17f00df0: Pull complete
8e5e30d57895: Pull complete
a1fd179b16c6: Pull complete
7496d9eb4150: Pull complete
0328931819fd: Pull complete
8acde85a664a: Pull complete
38e831e7d2d3: Pull complete
582b4ba3b134: Pull complete
cbf69ccc1db5: Pull complete
1e1f3255b2e0: Pull complete
c1c0cedd64ec: Pull complete
6adde56874ed: Pull complete
Digest: sha256:110d3325db02daa6e1541fdd37725fcbecb7d51411229d922562f208c51d35cc
Status: Downloaded newer image for postgres:latest
Pulling web (sanposhiho/phoenix:)...
latest: Pulling from sanposhiho/phoenix
50e431f79093: Already exists
dd8c6d374ea5: Already exists
c85513200d84: Already exists
55769680e827: Already exists
f5e195d50b88: Already exists
f7e2598a9cb7: Already exists
9ba52fdf113f: Already exists
896d0883eede: Already exists
019ae449ef4b: Already exists
a653e3c2dbc7: Pull complete
1b5116636524: Pull complete
6a7182c301e9: Pull complete
ff51ec8f406c: Pull complete
4c53f0b7d33e: Pull complete
79b95deb3b15: Pull complete
4e0c0135d3e7: Pull complete
Digest: sha256:ab7dbe3a514597f3e390f90de76de6465defb90103f58c3f08e34db97d890ae7
Status: Downloaded newer image for sanposhiho/phoenix:latest
Creating docker-elixir_db_1 ... done
Creating docker-elixir_web_1 ... done

このように sanposhiho/phoenix がなくても勝手に dockerhub から取ってきてくれます。

Kubernetes をやっていく

以下の記事を参考に先ほどの環境を Kubernetes(minikube)でも動かしてみます。 Docker Compose から Minikube + Kompose に移行してみよう

Kompose と言うのは docker-compose.yml を Kubernetes 向けの設定ファイルに変換してくれる便利なやつです。

そもそも Kubernetes って?

色々見ましたが以下の記事の前半部分の説明がとても分かり易かったです 数時間で完全理解!わりとゴツい Kubernetes ハンズオン!!

Kompose を使う前に色々修正

Dockerfile

FROM elixir:1.10.2

RUN curl -sL https://deb.nodesource.com/setup_12.x | bash

RUN apt-get install -y nodejs

RUN npm install [email protected] -g

RUN mix local.hex --force

RUN mix archive.install hex phx_new 1.4.12 --force

RUN mix local.rebar --force

RUN apt-get install ca-certificates  #追加

COPY . /app                          #追加

WORKDIR /app

これを先ほどの手順で dockerhub に上げます (僕は sanposhiho/phoenixfork8s として上げました。)

docker-compose.yml

version: "3"
services:
  web:
    image: sanposhiho/phoenix_for_k8s #変更
    ports:
      - "4000:4000"
    command: mix phx.server
    depends_on:
      - db

  db:
    image: postgres
    ports:
      - "5432:5432" #追加
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_HOST=db

追加/変更の他に volume の部分が削除されています。

kompose で変換

$ kompose convert
INFO Kubernetes file "db-service.yaml" created
INFO Kubernetes file "web-service.yaml" created
INFO Kubernetes file "db-deployment.yaml" created
INFO Kubernetes file "web-deployment.yaml" created

幾つかのファイルが作成されました。

生成されたファイルを微修正

web-service.yamlに以下を追記します。

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.21.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: web
  name: web
spec:
  ports:
    - name: "4000"
      port: 4000
      targetPort: 4000
  selector:
    io.kompose.service: web
  type: NodePort #追加
status:
  loadBalancer: {}

これにより外の世界からアクセス可能になります。

生成されたファイルを見ていく

Kompose が生成してくれたファイルを見ていきます。 以下の公式ドキュメントが理解の上で役立つと思います。 Kubernetes | Kubernetes オブジェクトを理解する

Kompose によって大きく分けて「Deployment」と「Service」の 2 つが作成されています。

Deployment とは

Deployment に関しては以下の公式ドキュメントがわかりやすいです。 Kubernetes | Deployment

以下の記事も(少し古いですが)とても参考になりました。 Kubernetes: Deployment の仕組み

deployment は pod(Kubernetes の管理する最小単位)を管理します。 (正確には pod を管理する ReplicaSet を作成します。)

実際に作成された web-deployment.yaml を見てみます。

apiVersion: apps/v1 #どのバージョンのKubernetesAPIを利用するか
kind: Deployment #何についての定義ファイルか
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.21.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: web
  name: web #deploymentの名前
spec:
  replicas: 1 #replicaの数
  selector:
    matchLabels:
      io.kompose.service: web #podのラベル定義
  strategy: {}
  template: #deploymentが管理するpodの定義
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.21.0 ()
      creationTimestamp: null
      labels:
        io.kompose.service: web
    spec:
      containers:
        - args:
            - mix
            - phx.server
          image: sanposhiho/phoenix_for_k8s
          imagePullPolicy: ""
          name: web
          ports:
            - containerPort: 4000
          resources: {}
      restartPolicy: Always
      serviceAccountName: ""
      volumes: null
status: {}

web-deployment.yamlでは spec.template で指定された pod を常に 1 つ維持するようにしています。

Service とは

以下の公式ドキュメントが参考になります。 Kubernetes | Service

Pod はそれぞれが IP アドレスを持っています。例えば今回のように DB の Pod と Web サーバーの Pod に別れている場合、Web サーバーが DB の Pod にアクセスするには DB の Pod の IP アドレスが必要になります。

そこで Service は pod たちをセットで管理し(「DB の Pod」「サーバーの Pod」と言う風に管理)、そのセットに対してのアクセスが可能になります。 例え Pod が動的に入れ替わったりしても一貫した方法でのアクセスが可能になります。 (Service無しだと、何かの障害で 1 つの Pod が死んで、Deployment が代わりの Pod に入れ替えた時には IP アドレスが変わってしまうのでアクセスができなくなってしまいます)

実際に作成された web-service.yaml をみてみます。

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.21.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: web
  name: web
spec:
  ports:                      #管理するportに関して
  - name: "4000"
    port: 4000
    targetPort: 4000
  selector:                   #管理するPodの指定
    io.kompose.service: web
  type: NodePort
status:
  loadBalancer: {}

先ほど追加した type: NodePort は指定しなかった場合デフォルト値として ClusterIP に指定されます。

ClusterIP: クラスター内部の IP で Service を公開する。このタイプでは Service はクラスター内部からのみ疎通性があります。

これではクラスターの外部からのアクセスができなかったため NodePort に変更しました

NodePort: 各 Node の IP にて、静的なポート(NodePort)上で Service を公開します。その NodePort の Service が転送する先の ClusterIP Service が自動的に作成されます。<NodeIP>:<NodePort>にアクセスすることによって NodePort Service にアクセスできるようになります。

Service の公開 (Service のタイプ)

minikube を立ち上げておく

$ minikube start

ダッシュボードを開いておく

$ minikube dashboard

ダッシュボードを使えば以下のように Pod などの状態をブラウザから確認できます。 スクリーンショット 2020-03-08 18.22.25.png

立ち上げ!

ついに Kubernetes 上で立ち上げてみます。

$ kubectl apply -f db-deployment.yaml
$ kubectl apply -f web-deployment.yaml
$ kubectl apply -f db-service.yaml
$ kubectl apply -f web-service.yaml

これによってファイルに定義されたもの達が立ち上がります。

kensei-mba:docker-elixir nakatakensei$ kubectl get all
NAME                      READY   STATUS    RESTARTS   AGE
pod/db-5fbcf655cd-2k7lw   1/1     Running   0          159m
pod/web-87795996-r6rcf    1/1     Running   0          159m


NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/db           ClusterIP   10.111.98.119   <none>        5432/TCP         159m
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          19h
service/web          NodePort    10.107.156.58   <none>        4000:30249/TCP   159m


NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/db    1/1     1            1           159m
deployment.apps/web   1/1     1            1           159m

NAME                            DESIRED   CURRENT   READY   AGE
replicaset.apps/db-5fbcf655cd   1         1         1       159m
replicaset.apps/web-87795996    1         1         1       159m

DB を作成する

kubectl exec -it web-87795996-r6rcf mix ecto.create

kubectl exec -it <Pod NAME> <command>で任意のコードを Pod に対して実行させることができます。 また、このコードが通る=Service が機能して DB に繋いでくれていることを意味します。

ちゃんと立ち上がっているか確認

$ minikube service list
|----------------------|---------------------------|--------------|---------------------------|
|      NAMESPACE       |           NAME            | TARGET PORT  |            URL            |
|----------------------|---------------------------|--------------|---------------------------|
| default              | db                        | No node port |
| default              | kubernetes                | No node port |
| default              | web                       |              | http://192.168.64.2:32566 |
| kube-system          | kube-dns                  | No node port |
| kubernetes-dashboard | dashboard-metrics-scraper | No node port |
| kubernetes-dashboard | kubernetes-dashboard      | No node port |
|----------------------|---------------------------|--------------|---------------------------|

web の URL にアクセスします スクリーンショット 2020-03-08 15.52.53.png このように Phoenix の Top 画面が表示されれば成功です!

これでも動いてはいますが…

現状の設定では DB の Pod 内のみに DB のデータが存在します。 なので DB の Pod が死んだ時に全てのデータが死んでしまいます。

1 回実験してみましょう

スクリーンショット 2020-03-08 20.12.00.png

ダッシュボードから作成されているKubernetes 以外のService, Pod, deployment を全て削除してください。

以下のようになれば合っています。 スクリーンショット 2020-03-08 20.05.10.png

Web アプリケーションを DB を使うアプリケーションに作り直す

Phoenix にも Rails と同様に便利な generator の機能が搭載されています。

ローカルで generator を使用します。

$ mix phx.gen.html Blog Post posts title:string content:string
* creating lib/dododo_web/controllers/post_controller.ex
* creating lib/dododo_web/templates/post/edit.html.eex
* creating lib/dododo_web/templates/post/form.html.eex
* creating lib/dododo_web/templates/post/index.html.eex
* creating lib/dododo_web/templates/post/new.html.eex
* creating lib/dododo_web/templates/post/show.html.eex
* creating lib/dododo_web/views/post_view.ex
* creating test/dododo_web/controllers/post_controller_test.exs
* creating lib/dododo/blog/post.ex
* creating priv/repo/migrations/20200308110013_create_posts.exs
* creating lib/dododo/blog.ex
* injecting lib/dododo/blog.ex
* creating test/dododo/blog_test.exs
* injecting test/dododo/blog_test.exs

Add the resource to your browser scope in lib/dododo_web/router.ex:

    resources "/posts", PostController


Remember to update your repository by running migrations:

    $ mix ecto.migrate

書かれているように router.ex にルーティングを追加しておきます。

defmodule DododoWeb.Router do
  use DododoWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", DododoWeb do
    pipe_through :browser

    get "/", PageController, :index
    resources "/posts", PostController   #追加
  end

  # Other scopes may use custom stacks.
  # scope "/api", DododoWeb do
  #   pipe_through :api
  # end
end

migration します

$ mix ecto.migrate

11:23:37.327 [info]  == Running 20200308110013 Dododo.Repo.Migrations.CreatePosts.change/0 forward

11:23:37.335 [info]  create table posts

11:23:37.392 [info]  == Migrated 20200308110013 in 0.0s

これで /posts にアクセスすると以下のようなアプリが作成できています (画像は New Post から新たな post を作成した後です) スクリーンショット 2020-03-08 20.29.24.png

この変更を dockerhub の image に反映させます。 先ほど説明した手順とほとんど同じなのでコマンドだけ載せておきます。

$ docker build .
$ docker images   #image idを取得
$ docker tag <image id> sanposhiho/phoenix_for_k8s
$ docker push sanposhiho/phoenix_for_k8s

minikube 環境で変更後のアプリケーションを動かす

こちらもほぼ手順が変わらないのでコマンドだけ載せておきます。

$ kubectl apply -f db-deployment.yaml
$ kubectl apply -f web-deployment.yaml
$ kubectl apply -f db-service.yaml
$ kubectl apply -f web-service.yaml
$ kubectl get pods   #pod nameの確認
$ kubectl exec -it <Pod NAME> mix ecto.create
$ kubectl exec -it <Pod NAME> mix ecto.migrate

先ほどと違うのは最後に $ kubectl exec -it <Pod NAME> mix ecto.migrate が追加されていることです。これによって posts テーブルが DB の Pod 内に作成されます。

画像使い回しですが、以下のページが/posts から確認できれば成功です。

hoge.png

DB の Pod を削除してみる

ダッシュボードから DB の Pod を削除します。 eee.png

Deployment によってすぐに新しい DB 用の Pod が作られます。(さすが)

さて、先ほどのページを開き直してみるとどうなっているでしょうか qqqqq.png

訳のわからんエラーが出ています。 「何回か DB にアクセスしようとしたけど、無理でしたー」というエラーです。

無事に(?)DB が Pod が死んだことで消えてしまったことがわかりました。

ちなみに以下のコマンドで DB を作り直して Posts テーブルを再作成すると先ほどの「ほげほげ」のデータは残っていませんが、ページが正常に表示されます。 (作り直された DB の Pod に新しく出来た DB だから当たり前ですね)

$ kubectl get pods   #pod nameの確認
$ kubectl exec -it <Pod NAME> mix ecto.create
$ kubectl exec -it <Pod NAME> mix ecto.migrate

hoge.png

volume を設定して DB の揮発を防ぐ

長々実験しましたが、この DB の揮発(=永続の逆。Pod が死ぬと DB も一緒に消えてしまうと言う意)を防ぐには volume を設定する必要があります。

volume の設定方法ですが 2 つ存在しました。 (どっちがいいのかは分からないです…どなたか教えてください。)

  1. db-development.yamlの volumes のみを弄る
  2. PersistentVolumeClaimを利用する

1. db-development.yamlの volumes のみを弄る

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.21.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: db
  name: db
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: db
  strategy: {}
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.21.0 ()
      creationTimestamp: null
      labels:
        io.kompose.service: db
    spec:
      containers:
      - env:
        - name: POSTGRES_HOST
          value: db
        - name: POSTGRES_PASSWORD
          value: postgres
        - name: POSTGRES_USER
          value: postgres
        image: postgres
        imagePullPolicy: ""
        name: db
        ports:
        - containerPort: 5432
        volumeMounts:                     #追加
          - mountPath: "/var/lib/postgresql/data"
            name: pgdata
        resources: {}
      restartPolicy: Always
      serviceAccountName: ""
      volumes:                         #追加
        - name: pgdata
          hostPath:
            path: /Users/nakatakensei/docker-elixir/  #postgresのdataをhostのどこに置いておくか

2. PersistentVolumeClaimを利用する

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pv-claim
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.21.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: db
  name: db
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: db
  strategy: {}
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.21.0 ()
      creationTimestamp: null
      labels:
        io.kompose.service: db
    spec:
      containers:
      - env:
        - name: POSTGRES_HOST
          value: db
        - name: POSTGRES_PASSWORD
          value: postgres
        - name: POSTGRES_USER
          value: postgres
        image: postgres
        imagePullPolicy: ""
        name: db
        ports:
        - containerPort: 5432
        volumeMounts:
          - mountPath: "/var/lib/postgresql/data"
            name: pgdata
        resources: {}
      restartPolicy: Always
      serviceAccountName: ""
      volumes:
        - name: pgdata
          persistentVolumeClaim:   #1とはここが違う
            claimName: pv-claim

先ほどの手順で実験してもらうとどちらの方法を用いても DB 揮発しちゃう問題が解決したことがわかります。 (スクショを撮っても分かり難かったため、ここまで実際に手を動かして進めていただいた方は自分で実験してみてください)

終わりに

最終的なファイル達は以下のリポジトリに上がっています。 https://github.com/sanposhiho/docker-elixir-phoenix

すごく長い記事になってしまいました。 しかし、個人的に Docker→Kubernetes と一緒の流れで学べるようなチュートリアルが無かったため記事を分けずにこのように進めました。

どなたかの役に立てば幸いです。

参考

記事内であげなかったけどチラチラ見て参考にさせていただいたサイトです。

Docker で Ruby on Rails の開発をしよう kubernetes クラスタで Rails アプリを公開するチュートリアル Kubernetes の永続化 [PersistentVolume/PersistentVolumeClaim]

このエントリーをはてなブックマークに追加