Kubernetes入門|yamlとコマンドの基本的な使い方と運用効率化術

KUBERNETES

サイバーエージェントで社内のプライベートクラウド構築に携わるほか、Kubernetesのマネージドサービスもオンプレミス上で実装して提供している青山真也(@amsy810)と申します。外部でもKubernetesの仕事を複数行っているほか、コミュニティ活動、DockerやKubernetesに関する本を2冊執筆するなど積極的に布教活動を行っています。

以前「Dockerとコンテナがざっくりわかる!『Kubernetes完全ガイド』青山さんにFLEXYの麻衣子お姉さんが聞く! #Docker編」でDockerとKubernetesの基本についてお伝えしました。今回はさらに次のステップに進めるような内容をお伝えします。

ユーザーの追加・表示をするGo製アプリケーションを作ってみる

今回題材にするのは、簡易的なGoのアプリケーションです。中身はシンプルなもので、メイン関数で8080番ポートにリクエストが来たときのハンドラを用意しています。それぞれのパスに関するハンドラは、ルート直下の / は200を返すだけのもので、今回実際に利用するのは/getuserと/adduserの2種類のパスに対応する関数です。

addUserは新規のユーザーを1つ追加する動作をさせます。go-randomdataというライブラリを使い、ランダムな名前を生成してデータベースに追加します。また、同時にデータベースにIDが自動採番されるようになっています。

getUser関数は名前の通り、ユーザー名を取得してきて表示します。その際は、データベースに登録されている複数のユーザー名からランダムに選択した1件のidとnameを取得します。

Goで用意したアプリケーションはこちらです。

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "github.com/Pallinder/go-randomdata"
  6. _ "github.com/go-sql-driver/mysql"
  7. "net/http"
  8. "os"
  9. )
  10. var (
  11. dbuser string
  12. dbpass string
  13. dbhost string
  14. dbport string
  15. dbname string
  16. hostname string
  17. )
  18. func rootHandler(w http.ResponseWriter, r *http.Request) {
  19. fmt.Fprintf(w, "/ is requested")
  20. }
  21. func getUserHandler(w http.ResponseWriter, r *http.Request) {
  22. id, name := getUser()
  23. fmt.Fprintf(w, fmt.Sprintf("%s: name is [%d %s]\n", hostname, id, name))
  24. fmt.Printf(fmt.Sprintf("%s: name is [%d %s]\n", hostname, id, name))
  25. }
  26. func addUserHandler(w http.ResponseWriter, r *http.Request) {
  27. name := addUser()
  28. fmt.Fprintf(w, fmt.Sprintf("%s: added user [%s]\n", hostname, name))
  29. fmt.Printf(fmt.Sprintf("%s: added user [%s]\n", hostname, name))
  30. }
  31. func main() {
  32. getFromEnv()
  33. http.HandleFunc("/", rootHandler)
  34. http.HandleFunc("/getuser", getUserHandler)
  35. http.HandleFunc("/adduser", addUserHandler)
  36. http.ListenAndServe(":8080", nil)
  37. }
  38. func getFromEnv() {
  39. dbuser = os.Getenv("DBUSER")
  40. dbpass = os.Getenv("DBPASS")
  41. dbhost = os.Getenv("DBHOST")
  42. dbport = os.Getenv("DBPORT")
  43. dbname = os.Getenv("DBNAME")
  44. hostname, _ = os.Hostname()
  45. }
  46. func getUser() (int, string) {
  47. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbuser, dbpass, dbhost, dbport, dbname))
  48. if err != nil {
  49. panic(err.Error())
  50. }
  51. defer db.Close()
  52. stmtOut, err := db.Prepare("SELECT id,name FROM users ORDER BY RAND() LIMIT 1;")
  53. if err != nil {
  54. panic(err.Error())
  55. }
  56. defer stmtOut.Close()
  57. var name string
  58. var id int
  59. err = stmtOut.QueryRow().Scan(&id, &name)
  60. if err != nil {
  61. panic(err.Error())
  62. }
  63. return id, name
  64. }
  65. func addUser() string {
  66. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbuser, dbpass, dbhost, dbport, dbname))
  67. if err != nil {
  68. return err.Error()
  69. }
  70. defer db.Close()
  71. stmtIns, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
  72. if err != nil {
  73. return err.Error()
  74. }
  75. defer stmtIns.Close()
  76. name := randomdata.SillyName()
  77. _, err = stmtIns.Exec(name)
  78. if err != nil {
  79. return err.Error()
  80. }
  81. return name
  82. }

コンテナに対応しやすいアプリケーションを作るために、データベースのユーザー名・パスワード・ポートなどは環境変数で指定するようにしてあります。

このアプリケーションをコンテナ化するためにまず下記のようなDockerfileを作成します。なお、今回は Go 1.13をベースイメージとして利用します。

  1. FROM golang:1.13
  2. WORKDIR /go/src/app
  3. COPY ./main.go .
  4. RUN go get -d -v github.com/go-sql-driver/mysql
  5. RUN go get -d -v github.com/Pallinder/go-randomdata
  6. RUN go build -o /app ./main.go
  7. CMD ["/app"]

今回のファイルはGitHub上で公開しているのでご覧ください。

Dockerfileが書けたあとは、いよいよコンテナイメージのビルドを行います。ビルドはdocker buildコマンドを利用します。ビルドの完了後、それをコンテナレジストリに追加すればネットワーク経由で使えるようになります。 コンテナのイメージ作りはこれで完成です。このイメージがあればGKEでもローカル環境でもどこでも同じものを動かすことができます。

Kubernetesの基本的な使い方

ここからKubernetesが登場です。Kubernetesでコンテナをいい感じに配置したりしていきます。まずはスモールスタートで先ほどのアプリケーションを動かしていきましょう。

Podを作る

Kubernetesの最小単位のリソースはPodです。1つのPodの中にコンテナを1つだけ入れる 場合もあれば、複数のコンテナが入っている場合もあります。複数のコンテナを入れる際には、 同じコンテナを複数組み合わせるのではなく、役割が違うものを組み合わせて使います。

VM的な考え方と照らし合わせると、PodはVM、コンテナはプロセスに相当する関係と言えま す。Podとコンテナの関係は、VMとプロセスの考え方に近いのです。そのため、初心者はこの 考え方に基づいたマニフェストになりがち。ここがポイントになるのですが、実はこういう 使い方をすることはKubernetesでは良い使い方ではないのです。

よくある例では、アプリケーションサーバとデータベースが一緒に入ってしまっているパターンで、VM時代であれば1台の中にデータベースもアプリケーションも入れてしまっているのと同じような使い方をしてしまう形ですね。Kubernetesでは「こういった使い方は良くない」のですが、今回はとっつきやすさを優先した第一歩ということで、このパターンでやっていきましょう。

Podにはコンテナを複数入れられるので、アプリケーションコンテナとMySQLのコンテナを用意し、Pod定義の中にコンテナを追加していくことで同等の構成がとれます。ちょうどVMの中に2つのプロセスを起動させるのと同じです。 アプリケーションコンテナには環境変数を用意し、どこのデータベースに接続しにいくかの情報を持たせています。VMの場合と同じように、データベースの認証情報やホスト情報を指定します。

yaml形式で書かれたマニフェストファイルで示すとこのようになります。

  1. ---
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: init-db-sql
  6. data:
  7. create_usertable.sql: |
  8. CREATE TABLE IF NOT EXISTS mydb.users (id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(50));
  9. ---
  10. apiVersion: v1
  11. kind: Pod
  12. metadata:
  13. name: sample-pod
  14. labels:
  15. role: all-in-one
  16. spec:
  17. containers:
  18. - name: app-container
  19. image: masayaaoyama/flexy-demo-app:v1.0
  20. imagePullPolicy: Always
  21. env:
  22. - name: DBHOST
  23. value: 127.0.0.1
  24. - name: DBPORT
  25. value: "3306"
  26. - name: DBUSER
  27. value: myuser
  28. - name: DBPASS
  29. value: mypass
  30. - name: DBNAME
  31. value: mydb
  32. - name: mysql-container
  33. image: mysql:8.0
  34. env:
  35. - name: MYSQL_ROOT_PASSWORD
  36. value: rootpass
  37. - name: MYSQL_DATABASE
  38. value: mydb
  39. - name: MYSQL_USER
  40. value: myuser
  41. - name: MYSQL_PASSWORD
  42. value: mypass
  43. volumeMounts:
  44. - name: init-sql-configmap
  45. mountPath: /docker-entrypoint-initdb.d
  46. volumes:
  47. - name: init-sql-configmap
  48. configMap:
  49. name: init-db-sql
  50. ---
  51. apiVersion: v1
  52. kind: Service
  53. metadata:
  54. name: flexy-demo-all-in-one
  55. spec:
  56. type: LoadBalancer
  57. ports:
  58. - name: "http-port"
  59. protocol: "TCP"
  60. port: 8080
  61. targetPort: 8080
  62. selector:
  63. role: all-in-one

今回の場合だと、localhostを指定して接続するようにしています。

Kubernetes01
アプリケーションコンテナからlocalhostの3306ポートでMySQLに接続させます。

なお、MySQLのコンテナはわざわざ自分でイメージを作るのではなく、公式に提供されているイメージを使います。コンテナはこのような公式のイメージがいろいろと提供されており、なにか機能を使いたいときには自ら用意しなくても公式のイメージを利用できるのは利点の一つです。もちろん、そのイメージが脆弱なものでないかは利用前に確認は必ず行いましょう。

今回のMySQLのコンテナは、rootのパスワードや起動初期に作るデータベース、ユーザー・パスワードなどは環境変数として指定できるようになっています。そのため、今回の例だとMySQLコンテナを起動した段階でmydbというデータベースが作られ、myuserがmypassというパスワードを持って接続できるようになります。

次に、volumeMountsという部分に注目してみましょう。これはMySQLコンテナが起動するタイミングで、MySQLコンテナの /docker-entrypoint-initdb.d にこのボリュームファイルを関連付ける設定です。これによって指定のボリュームの中身をマウントしてくれます。どのファイルを使うかは、ConfigMapにて指定します。

今回の例だと、create_usertable.sql を指定しているように、コンテナ起動時にMySQLのテーブルを作って、アプリケーションコンテナからそこにアクセスできる状況を作り出しています。

本来であればアプリケーションのマイグレーションもあるので、適切なやり方ではありませんが、今回は簡単なサンプルのためこのようにしています。

また、KubernetesにはServiceというリソースがあります。いろいろな種類がありますが、今回はL4のロードバランサーを使っています。このロードバランサーで8080できたリクエストをコンテナのポート、今回は同じく8080に転送します。

どのPodに対してリクエストを転送するかはselectorで指定します。

Kubernetes02
それでは、Kubernetesで作ったマニフェスト使ってデプロイしていきます。まず最初にKubernetesのノードと、その上で動いているPodを確認してみます。

  1. $ kubectl get nodes
  2. NAME STATUS ROLES AGE VERSION
  3. gke-flexy-demo-default-pool-5d473a6b-3pz7 Ready 8m28s v1.15.4-gke.22
  4. gke-flexy-demo-default-pool-5d473a6b-h3s5 Ready 8m22s v1.15.4-gke.22
  5. gke-flexy-demo-default-pool-5d473a6b-l580 Ready 8m28s v1.15.4-gke.22
  6. $ kubectl get pods
  7. No resources found in default namespace.

この段階ではまだPodはありません。

いよいよ作成したマニフェストをKubernetesに登録します。

  1. $ kubectl apply -f 1_pod.yaml
  2. configmap/init-db-sql created
  3. pod/sample-pod created
  4. service/flexy-demo-all-in-one created
  5. $ kubectl get services
  6. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  7. flexy-demo-all-in-one LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:31945/TCP 2m45s
  8. kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 12m
  9. $ kubectl get pods
  10. NAME READY STATUS RESTARTS AGE
  11. sample-pod 2/2 Running 0 5m22s

すると、2つのコンテナが入ったPodが1個立ち上がってきます。合わせてロードバランサーも払い出され、インターネットからアクセス可能なグローバルIPアドレスも払い出されています。

次にユーザーの追加と削除を試します。最初の時点ではユーザーは一切登録されていないため、ブラウザなどから http://XXX.XXX.XXX.XXX:8080/adduser に5回アクセスして初期ユーザーを作成しましょう。 その後、以下のように http://XXX.XXX.XXX.XXX:8080/getuser に対して十分な数のリクエストを送ると、5種類のランダムなユーザーが生成されていることが確認できます。

  1. $ for i in `seq 1 100`; do curl -s http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
  2. sample-pod: name is [1 Gorillascythe]
  3. sample-pod: name is [2 Cubrowan]
  4. sample-pod: name is [3 Chintabby]
  5. sample-pod: name is [4 Cloudbutter]
  6. sample-pod: name is [5 Samurailily]

ステップ1の実験が終わったら一旦不要なものは削除します。

  1. $ kubectl delete -f 1_pod.yaml
  2. configmap "init-db-sql" deleted
  3. pod "sample-pod" deleted
  4. service "flexy-demo-all-in-one" deleted

ここまでが最小構成のものです。

Deploymentでスケーリング

Podが1個だけでは何もうまみが感じられません。スケーリングさせることにより、利便性が出てきます。その際、アプリケーションとデータベースの機能の両方ともをスケールさせる機会は少ないでしょう。アプリケーションはいくつ、データベースはいくつと、それぞれ分割して考えていくことでしょう。そのため、アプリケーションとデータベースのコンテナは別々のPodにするべきです。

設定をマニフェストに落とし込むとこのようになります。

  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: flexy-demo-app
  6. spec:
  7. replicas: 1
  8. selector:
  9. matchLabels:
  10. role: app
  11. template:
  12. metadata:
  13. labels:
  14. role: app
  15. spec:
  16. containers:
  17. - name: app-container
  18. image: masayaaoyama/flexy-demo-app:v1.0
  19. imagePullPolicy: Always
  20. env:
  21. - name: DBHOST
  22. value: mysql.default.svc.cluster.local
  23. - name: DBPORT
  24. value: "3306"
  25. - name: DBUSER
  26. value: myuser
  27. - name: DBPASS
  28. value: mypass
  29. - name: DBNAME
  30. value: mydb
  31. ---
  32. apiVersion: v1
  33. kind: Service
  34. metadata:
  35. name: flexy-demo-app
  36. spec:
  37. type: LoadBalancer
  38. ports:
  39. - name: "http-port"
  40. protocol: "TCP"
  41. port: 8080
  42. targetPort: 8080
  43. selector:
  44. role: app
  45. ---
  46. apiVersion: v1
  47. kind: ConfigMap
  48. metadata:
  49. name: init-db-sql
  50. data:
  51. create_usertable.sql: |
  52. CREATE TABLE IF NOT EXISTS mydb.users (id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(50));
  53. ---
  54. apiVersion: apps/v1
  55. kind: Deployment
  56. metadata:
  57. name: mysql
  58. spec:
  59. replicas: 1
  60. selector:
  61. matchLabels:
  62. role: db
  63. template:
  64. metadata:
  65. labels:
  66. role: db
  67. spec:
  68. containers:
  69. - name: mysql-container
  70. image: mysql:8.0
  71. env:
  72. - name: MYSQL_ROOT_PASSWORD
  73. value: rootpass
  74. - name: MYSQL_DATABASE
  75. value: mydb
  76. - name: MYSQL_USER
  77. value: myuser
  78. - name: MYSQL_PASSWORD
  79. value: mypass
  80. volumeMounts:
  81. - name: init-sql-configmap
  82. mountPath: /docker-entrypoint-initdb.d
  83. volumes:
  84. - name: init-sql-configmap
  85. configMap:
  86. name: init-db-sql
  87. ---
  88. apiVersion: v1
  89. kind: Service
  90. metadata:
  91. name: mysql
  92. spec:
  93. type: ClusterIP
  94. ports:
  95. - name: "mysql-port"
  96. protocol: "TCP"
  97. port: 3306
  98. targetPort: 3306
  99. selector:
  100. role: db

よく見ると先ほどのものと書いてある内容は大きく変わっておらず、アプリケーションとデータベースがそれぞれ分割されて細かく構造化されていったものです。また、先程はPodだったものが、Deploymentに変わっています。このDeploymentは、templateで指定されたPodの構成を複製するための機能になります。VMの場合は、インスタンスグループに近いイメージですね。Deploymentでは、レプリカの数を指定したり下記の図のようにどのPodをカウントするのかをselectorで指定します。templateにはPodを起動するときにrole:appラベルが付与されて起動してくるように指定されているので正しくカウントできるようになっています。

Kubernetes03
さてここで懸念点が発生します。アプリケーション用のコンテナから、データベース用のコンテナに対してはどのように接続したら良いのでしょうか。

最初の段階ではlocalhost宛に接続していましたが、今はもう同じPod内にはデータベースがいません。ここでもServiceを用いたロードバランサーを利用していきます。先程はグローバルIPアドレスを持ったロードバランサーを作成しましたが、実はKubernetesクラスタ内のみで利用可能な内部ロードバランサーを作成することも可能です。外部向けのロードバランサーを作成するときはServiceの設定時に spec.type: LoadBalancer、内部向けの場合には spec.type: ClusterIP を指定しましょう。また、内部向けロードバランサーの場合には、払い出されたIPアドレスの名前解決も行えるようになっています。いちいちIPアドレスを確認する必要がなくなるため、積極的に利用しましょう。

今回の場合には、アプリケーションから接続するデータベースの接続先はmysql.default.svc.cluster.localです。この名前の解釈の仕方は、先頭の「mysql」はMySQLのデータベースに対応するServiceの名前であることを示し、その後ろにネームスペース、その後に svc.cluster.local と続きます。

Kubernetes04
では実際に試してみましょう。

  1. $ kubectl apply -f 2_deployment.yaml
  2. deployment.apps/flexy-demo-app created
  3. service/flexy-demo-app created
  4. configmap/init-db-sql created
  5. deployment.apps/mysql created
  6. service/mysql created
  7. $ kubectl get pods
  8. NAME READY STATUS RESTARTS AGE
  9. flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 8m36s
  10. mysql-5dd5b5c644-vtsjp 1/1 Running 0 8m35s
  11. $ kubectl get service
  12. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  13. flexy-demo-app LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:32591/TCP 8m37s
  14. kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 24m
  15. mysql ClusterIP XXX.XXX.XXX.XXX 3306/TCP 8m37s

このように2つのPodとServiceが立ち上がります。

今回1つのPodに対して1つのコンテナしか入ってないので「1/1」の表示ですが、先程のようにPodの中に複数のコンテナがあればここの表示が「2/2」になります。

アプリケーション宛のロードバランサーは外部向けのIPアドレスが払い出さるので、ここにアクセスすると先ほど同様にアプケーションが動作します。

先程はブラウザでやりましたが、もちろん curlコマンドを使ってもuserの登録は可能です。

  1. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  2. flexy-demo-app-5cb4bb64cd-md6zr: added user [Houndbird]
  3. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  4. flexy-demo-app-5cb4bb64cd-md6zr: added user [Stallionsticky]
  5. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  6. flexy-demo-app-5cb4bb64cd-md6zr: added user [Racernimble]
  7. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  8. flexy-demo-app-5cb4bb64cd-md6zr: added user [Salmonautumn]
  9. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  10. flexy-demo-app-5cb4bb64cd-md6zr: added user [Fingeode]
  11. $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
  12. flexy-demo-app-5cb4bb64cd-md6zr: name is [1 Houndbird]
  13. flexy-demo-app-5cb4bb64cd-md6zr: name is [2 Stallionsticky]
  14. flexy-demo-app-5cb4bb64cd-md6zr: name is [3 Racernimble]
  15. flexy-demo-app-5cb4bb64cd-md6zr: name is [4 Salmonautumn]
  16. flexy-demo-app-5cb4bb64cd-md6zr: name is [5 Fingeode]

Deploymentはレプリカ数をスケールさせることも可能です。 例えば、レプリカを3つにするには次のようなコマンドを実行しましょう。データベースは1つのままで、アプリケーションだけが3つになったことが確認できます。

  1. $ kubectl scale deployment flexy-demo-app --replicas 3
  2. deployment.extensions/flexy-demo-app scaled
  3. $ kubectl get pods
  4. NAME READY STATUS RESTARTS AGE
  5. flexy-demo-app-5cb4bb64cd-64t56 1/1 Running 0 47s
  6. flexy-demo-app-5cb4bb64cd-hphvf 1/1 Running 0 47s
  7. flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 10m
  8. mysql-5dd5b5c644-vtsjp 1/1 Running 0 10m

また同様にアプリケーションに対してリクエストを送ってみると、アプリケーションのホスト名も出力されているため、3*5 個の出力結果が確認できるかと思います。

  1. $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
  2. flexy-demo-app-5cb4bb64cd-64t56: name is [1 Houndbird]
  3. flexy-demo-app-5cb4bb64cd-64t56: name is [2 Stallionsticky]
  4. flexy-demo-app-5cb4bb64cd-64t56: name is [3 Racernimble]
  5. flexy-demo-app-5cb4bb64cd-64t56: name is [4 Salmonautumn]
  6. flexy-demo-app-5cb4bb64cd-64t56: name is [5 Fingeode]
  7. flexy-demo-app-5cb4bb64cd-hphvf: name is [1 Houndbird]
  8. flexy-demo-app-5cb4bb64cd-hphvf: name is [2 Stallionsticky]
  9. flexy-demo-app-5cb4bb64cd-hphvf: name is [3 Racernimble]
  10. flexy-demo-app-5cb4bb64cd-hphvf: name is [4 Salmonautumn]
  11. flexy-demo-app-5cb4bb64cd-hphvf: name is [5 Fingeode]
  12. flexy-demo-app-5cb4bb64cd-md6zr: name is [1 Houndbird]
  13. flexy-demo-app-5cb4bb64cd-md6zr: name is [2 Stallionsticky]
  14. flexy-demo-app-5cb4bb64cd-md6zr: name is [3 Racernimble]
  15. flexy-demo-app-5cb4bb64cd-md6zr: name is [4 Salmonautumn]
  16. flexy-demo-app-5cb4bb64cd-md6zr: name is [5 Fingeode]

図で表すと3つのPodが1つのMySQLを見に行くのでこのような状態です。

Kubernetes05
この状態では実際のサービスとしてはまだ不完全です。MySQLのPodを消した場合、データは消えてしまいます。 試しに一度MySQLのPodを削除してみましょう。自動的に新しいMySQLのPodは立ち上がってくるため、データベース自体は存在しています。しかし、ユーザーの一覧を取得しようとしても、データが全て消えてしまっていることが確認できるかと思います。

  1. $ kubectl delete pods mysql-5dd5b5c644-vtsjp
  2. pod "mysql-5dd5b5c644-vtsjp" deleted
  3. $ kubectl get pods
  4. NAME READY STATUS RESTARTS AGE
  5. flexy-demo-app-5cb4bb64cd-64t56 1/1 Running 0 10m
  6. flexy-demo-app-5cb4bb64cd-hphvf 1/1 Running 0 10m
  7. flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 20m
  8. mysql-5dd5b5c644-hiskx 1/1 Running 0 1m
  9. $ curl http://XXX.XXX.XXX.XXX:8080/getuser;
  10. curl: (52) Empty reply from server

この問題を解決するため、次はデータの永続化を行っていきましょう。その前に、このステップで作成したものを下記のコマンドを用いて削除しておきましょう。

  1. $ kubectl delete -f 2_deployment.yaml
  2. deployment.apps "flexy-demo-app" deleted
  3. service "flexy-demo-app" deleted
  4. configmap "init-db-sql" deleted
  5. deployment.apps "mysql" deleted
  6. service "mysql" deleted

Statefulで永続化

構成ファイルはほとんど同じですが、kindをStatefulSetにします。そのほかに、永続化Volumeの指定とマウント先の指定が必要になります。

ほとんどのKubernetes環境では、StatefulSetを使うことでPodを作る時にネットワーク越しに存在する永続化ボリュームを切り出してPodにアタッチしてくれます。今回はGKEを利用しているため、GCPのPersistentDiskから払い出されたディスクがPodの /var/lib/mysql にマウントされるような設定を行っています。

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: mysql
  5. spec:
  6. replicas: 1
  7. serviceName: mysql
  8. selector:
  9. matchLabels:
  10. role: db
  11. template:
  12. metadata:
  13. labels:
  14. role: db
  15. spec:
  16. containers:
  17. - name: mysql-container
  18. image: mysql:8.0
  19. env:
  20. - name: MYSQL_ROOT_PASSWORD
  21. value: rootpass
  22. - name: MYSQL_DATABASE
  23. value: mydb
  24. - name: MYSQL_USER
  25. value: myuser
  26. - name: MYSQL_PASSWORD
  27. value: mypass
  28. volumeMounts:
  29. - name: init-sql-configmap
  30. mountPath: /docker-entrypoint-initdb.d
  31. - name: datadir
  32. mountPath: /var/lib/mysql
  33. volumes:
  34. - name: init-sql-configmap
  35. configMap:
  36. name: init-db-sql
  37. volumeClaimTemplates:
  38. - metadata:
  39. name: datadir
  40. spec:
  41. accessModes:
  42. - ReadWriteOnce
  43. resources:
  44. requests:
  45. storage: 5G

それでは実際にこのマニフェストを登録してみましょう。

  1. $ kubectl apply -f 3_statefulset.yaml
  2. deployment.apps/flexy-demo-app created
  3. service/flexy-demo-app created
  4. configmap/init-db-sql created
  5. statefulset.apps/mysql created
  6. service/mysql created
  7. kubectl get pods
  8. NAME READY STATUS RESTARTS AGE
  9. flexy-demo-app-5cb4bb64cd-qxmwc 1/1 Running 0 14m
  10. mysql-0 1/1 Running 0 14m
  11. $ kubectl scale deployment flexy-demo-app --replicas 3
  12. deployment.extensions/flexy-demo-app scaled
  13. $ kubectl get pods
  14. NAME READY STATUS RESTARTS AGE
  15. flexy-demo-app-5cb4bb64cd-2hgbv 1/1 Running 0 11s
  16. flexy-demo-app-5cb4bb64cd-dwkgd 1/1 Running 0 11s
  17. flexy-demo-app-5cb4bb64cd-qxmwc 1/1 Running 0 14m
  18. mysql-0 1/1 Running 0 14m
  19. $ kubectl get svc
  20. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  21. flexy-demo-app LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:31831/TCP 14m
  22. kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 43m
  23. mysql ClusterIP XXX.XXX.XXX.XXX 3306/TCP 14m

先ほどまでは mysql−乱数 となっていたPod名が mysql-0 になっています。StatefulSetをスケールアウトさせると mysql-1 のようにカウントアップして増えていきます。逆にデータベースをスケールインする時には後ろの数が大きいものから消えていきます。このように、0は最初に作られて最後に消されるので、これをマスターにし、1以降をスレーブにするといった使い方が可能です。

それでは先程と同様の手順でにMySQL Podを停止してデータがそのままになるか試してみましょう。

  1. $ curl http://XXX.XXX.XXX.XXX:8080/adduser
  2. flexy-demo-app-5cb4bb64cd-dwkgd: added user [Spriteriver]
  3. $ curl http://XXX.XXX.XXX.XXX :8080/adduser
  4. flexy-demo-app-5cb4bb64cd-qxmwc: added user [Handpuddle]
  5. $ curl http://XXX.XXX.XXX.XXX :8080/adduser
  6. flexy-demo-app-5cb4bb64cd-dwkgd: added user [Wingruby]
  7. $ curl http://XXX.XXX.XXX.XXX :8080/adduser
  8. flexy-demo-app-5cb4bb64cd-dwkgd: added user [Ferretbattle]
  9. $ curl http://XXX.XXX.XXX.XXX :8080/adduser
  10. flexy-demo-app-5cb4bb64cd-dwkgd: added user [Maskroot]
  11. $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
  12. flexy-demo-app-5cb4bb64cd-2hgbv: name is [1 Spriteriver]
  13. flexy-demo-app-5cb4bb64cd-2hgbv: name is [2 Handpuddle]
  14. flexy-demo-app-5cb4bb64cd-2hgbv: name is [3 Wingruby]
  15. flexy-demo-app-5cb4bb64cd-2hgbv: name is [4 Ferretbattle]
  16. flexy-demo-app-5cb4bb64cd-2hgbv: name is [5 Maskroot]
  17. flexy-demo-app-5cb4bb64cd-dwkgd: name is [1 Spriteriver]
  18. flexy-demo-app-5cb4bb64cd-dwkgd: name is [2 Handpuddle]
  19. flexy-demo-app-5cb4bb64cd-dwkgd: name is [3 Wingruby]
  20. flexy-demo-app-5cb4bb64cd-dwkgd: name is [4 Ferretbattle]
  21. flexy-demo-app-5cb4bb64cd-dwkgd: name is [5 Maskroot]
  22. flexy-demo-app-5cb4bb64cd-qxmwc: name is [1 Spriteriver]
  23. flexy-demo-app-5cb4bb64cd-qxmwc: name is [2 Handpuddle]
  24. flexy-demo-app-5cb4bb64cd-qxmwc: name is [3 Wingruby]
  25. flexy-demo-app-5cb4bb64cd-qxmwc: name is [4 Ferretbattle]
  26. flexy-demo-app-5cb4bb64cd-qxmwc: name is [5 Maskroot]
  27. $ kubectl delete pods mysql-0
  28. pod "mysql-0" deleted
  29. $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
  30. flexy-demo-app-5cb4bb64cd-2hgbv: name is [1 Spriteriver]
  31. flexy-demo-app-5cb4bb64cd-2hgbv: name is [2 Handpuddle]
  32. flexy-demo-app-5cb4bb64cd-2hgbv: name is [3 Wingruby]
  33. flexy-demo-app-5cb4bb64cd-2hgbv: name is [4 Ferretbattle]
  34. flexy-demo-app-5cb4bb64cd-2hgbv: name is [5 Maskroot]
  35. flexy-demo-app-5cb4bb64cd-dwkgd: name is [1 Spriteriver]
  36. flexy-demo-app-5cb4bb64cd-dwkgd: name is [2 Handpuddle]
  37. flexy-demo-app-5cb4bb64cd-dwkgd: name is [3 Wingruby]
  38. flexy-demo-app-5cb4bb64cd-dwkgd: name is [4 Ferretbattle]
  39. flexy-demo-app-5cb4bb64cd-dwkgd: name is [5 Maskroot]
  40. flexy-demo-app-5cb4bb64cd-qxmwc: name is [1 Spriteriver]
  41. flexy-demo-app-5cb4bb64cd-qxmwc: name is [2 Handpuddle]
  42. flexy-demo-app-5cb4bb64cd-qxmwc: name is [3 Wingruby]
  43. flexy-demo-app-5cb4bb64cd-qxmwc: name is [4 Ferretbattle]
  44. flexy-demo-app-5cb4bb64cd-qxmwc: name is [5 Maskroot]

今回は正しくデータの永続化ができていますね。ここまでくると、ようやくよくある2層の構成ができました。 今回のMySQLはクラスタ化していませんが、MySQLもクラスタ化するようにしておくことで、データベースのStatefulSetもスケールさせることができるようになります。

Ingressでルーティング

最後に、Ingressについて紹介します。これは、Service を束ねる機能です。 簡単に言えば「リバプロ的な動きをするもの」で、アクセスを受けたホスト名やパスに対してどういうルーティングをするか指定できます。他にもKubernetesのSSL終端を任せることもできます。また、証明書をマニフェストとして登録しておくことも可能なため、証明書の交換も簡単に行うことが可能です。 下記の図だと productやuserの単位でマイクロサービスとしてどんどん横に増えていくイメージです。

Kubernetes06
ここまで来たら、マイクロサービスの土台ができたと言えるでしょう。

Kubernetesで効率的なサービス運用を!

今回は基本的な部分のみの紹介になりましたが、サービスを作っていく土台はできました。Kubernetesを駆使すればまだまだ便利で効率的なサービス運用ができるようになります。

いろいろと手を動かしながらKubernetesの素晴らしさを実感していってください。また、下記に様々なサンプルマニフェストも公開しているので、ぜひ利用してみてください。 https://github.com/MasayaAoyama/kubernetes-perfect-guide

IMG 9774

 

青山真也(@amsy810 株式会社サイバーエージェント AI事業本部 インフラエンジニア/Developer Experts(Kubernetes/CloudNative領域) OpenStackを使ったプライベートクラウドやGKE互換なコンテナプラットフォームをゼロから構築。国内カンファレンスでのKeynoteや海外カンファレンス等、登壇経験多数。世界で2番目にKubernetesの認定資格を取得。さくらインターネット研究所客員研究員、CREATIONLINE 技術アドバイザ、Contribute to K8s & OpenStack。著書に『Kubernetes完全ガイド』『みんなのDocker/Kubernetes』

FLEXYとはABOUT FLEXY

『FLEXY』はエンジニア・デザイナー・CTO・技術顧問を中心に
週2-3日 x 自社プロダクト案件を紹介するサービスです