Help us understand the problem. What is going on with this article?

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

はじめに

世はエンジニア戦国時代。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 リファレンス

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 npm@latest -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 npm@latest -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を参照するように設定すべきな訳です

「俺はMySQLをどうしても使いたいんや!」について

(3/10追記) 以下の記事が参考になるかもしれないです。
Phoenix on Kubernetesでpostgresでは無くMySQLを使う時のメモ

docker-composeを使用する

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

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

docker-compose.yml
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に変更しておきます。

config/dev.exs
# 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を修正します。

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

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 npm@latest -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/phoenix_for_k8sとして上げました。)

docker-compose.yml

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に以下を追記します。

web-servise.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」の二つが作成されています。

Deploymentとは

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

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

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

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

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無しだと、何かの障害で一つのPodが死んで、Deploymentが代わりのPodに入れ替えた時にはIPアドレスが変わってしまうのでアクセスができなくなってしまいます)

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

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が自動的に作成されます。:にアクセスすることによって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が死んだ時に全てのデータが死んでしまいます。

一回実験してみましょう

スクリーンショット 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にルーティングを追加しておきます。

lib/dododo_web/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の設定方法ですが二つ存在しました。
(どっちがいいのかは分からないです…どなたか教えてください。)

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

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

db-development.yaml
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を利用する

pvc-phoenix.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pv-claim
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
db-development.yaml
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と一緒の流れで学べるようなチュートリアルが無かったため記事を分けずにこのように進めました。

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

余談

ちなみに、この記事を読んでElixir/Phoenix勉強してみようかなって方がいましたら、この記事を元にDocker環境を立ててもいいですし、ローカルにElixir/Phoenixの環境を整えるには以下の記事が参考になるかと思います。
【2020/02最新】新品のMacBookでElixir(Phoenix)の環境を0から構築する

参考

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

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

sanpo_shiho
京都で大学生してます。
https://linktr.ee/sanpo_shiho
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした