Kubernetes入門|yamlとコマンドの基本的な使い方と運用効率化術
※本記事は2020年6月に公開された内容です。
青山真也(@amsy810)と申します。サイバーエージェントで社内のプライベートクラウド構築に携わるほか、Kubernetesのマネージドサービスもオンプレミス上で実装して提供しています。外部でもKubernetesの仕事を複数行っているほか、コミュニティ活動、DockerやKubernetesに関する本を2冊執筆するなど積極的に布教活動を行っています。
本記事ではDockerとKubernetesの基本からさらに次のステップに進めるような内容をお伝えします。
目次
ユーザーの追加・表示をするGo製アプリケーションを作ってみる
今回題材にするのは、簡易的なGoのアプリケーションです。中身はシンプルなもので、メイン関数で8080番ポートにリクエストが来たときのハンドラを用意しています。それぞれのパスに関するハンドラは、ルート直下の / は200を返すだけのもので、今回実際に利用するのは/getuserと/adduserの2種類のパスに対応する関数です。
addUserは新規のユーザーを1つ追加する動作をさせます。go-randomdataというライブラリを使い、ランダムな名前を生成してデータベースに追加します。また、同時にデータベースにIDが自動採番されるようになっています。
getUser関数は名前の通り、ユーザー名を取得してきて表示します。その際は、データベースに登録されている複数のユーザー名からランダムに選択した1件のidとnameを取得します。
Goで用意したアプリケーションはこちらです。
- package main
- import (
- "database/sql"
- "fmt"
- "github.com/Pallinder/go-randomdata"
- _ "github.com/go-sql-driver/mysql"
- "net/http"
- "os"
- )
- var (
- dbuser string
- dbpass string
- dbhost string
- dbport string
- dbname string
- hostname string
- )
- func rootHandler(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "/ is requested")
- }
- func getUserHandler(w http.ResponseWriter, r *http.Request) {
- id, name := getUser()
- fmt.Fprintf(w, fmt.Sprintf("%s: name is [%d %s]\n", hostname, id, name))
- fmt.Printf(fmt.Sprintf("%s: name is [%d %s]\n", hostname, id, name))
- }
- func addUserHandler(w http.ResponseWriter, r *http.Request) {
- name := addUser()
- fmt.Fprintf(w, fmt.Sprintf("%s: added user [%s]\n", hostname, name))
- fmt.Printf(fmt.Sprintf("%s: added user [%s]\n", hostname, name))
- }
- func main() {
- getFromEnv()
- http.HandleFunc("/", rootHandler)
- http.HandleFunc("/getuser", getUserHandler)
- http.HandleFunc("/adduser", addUserHandler)
- http.ListenAndServe(":8080", nil)
- }
- func getFromEnv() {
- dbuser = os.Getenv("DBUSER")
- dbpass = os.Getenv("DBPASS")
- dbhost = os.Getenv("DBHOST")
- dbport = os.Getenv("DBPORT")
- dbname = os.Getenv("DBNAME")
- hostname, _ = os.Hostname()
- }
- func getUser() (int, string) {
- db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbuser, dbpass, dbhost, dbport, dbname))
- if err != nil {
- panic(err.Error())
- }
- defer db.Close()
- stmtOut, err := db.Prepare("SELECT id,name FROM users ORDER BY RAND() LIMIT 1;")
- if err != nil {
- panic(err.Error())
- }
- defer stmtOut.Close()
- var name string
- var id int
- err = stmtOut.QueryRow().Scan(&id, &name)
- if err != nil {
- panic(err.Error())
- }
- return id, name
- }
- func addUser() string {
- db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbuser, dbpass, dbhost, dbport, dbname))
- if err != nil {
- return err.Error()
- }
- defer db.Close()
- stmtIns, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
- if err != nil {
- return err.Error()
- }
- defer stmtIns.Close()
- name := randomdata.SillyName()
- _, err = stmtIns.Exec(name)
- if err != nil {
- return err.Error()
- }
- return name
- }
コンテナに対応しやすいアプリケーションを作るために、データベースのユーザー名・パスワード・ポートなどは環境変数で指定するようにしてあります。
このアプリケーションをコンテナ化するためにまず下記のようなDockerfileを作成します。なお、今回は Go 1.13をベースイメージとして利用します。
- FROM golang:1.13
- WORKDIR /go/src/app
- COPY ./main.go .
- RUN go get -d -v github.com/go-sql-driver/mysql
- RUN go get -d -v github.com/Pallinder/go-randomdata
- RUN go build -o /app ./main.go
- 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形式で書かれたマニフェストファイルで示すとこのようになります。
- ---
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: init-db-sql
- data:
- create_usertable.sql: |
- CREATE TABLE IF NOT EXISTS mydb.users (id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(50));
- ---
- apiVersion: v1
- kind: Pod
- metadata:
- name: sample-pod
- labels:
- role: all-in-one
- spec:
- containers:
- - name: app-container
- image: masayaaoyama/flexy-demo-app:v1.0
- imagePullPolicy: Always
- env:
- - name: DBHOST
- value: 127.0.0.1
- - name: DBPORT
- value: "3306"
- - name: DBUSER
- value: myuser
- - name: DBPASS
- value: mypass
- - name: DBNAME
- value: mydb
- - name: mysql-container
- image: mysql:8.0
- env:
- - name: MYSQL_ROOT_PASSWORD
- value: rootpass
- - name: MYSQL_DATABASE
- value: mydb
- - name: MYSQL_USER
- value: myuser
- - name: MYSQL_PASSWORD
- value: mypass
- volumeMounts:
- - name: init-sql-configmap
- mountPath: /docker-entrypoint-initdb.d
- volumes:
- - name: init-sql-configmap
- configMap:
- name: init-db-sql
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: flexy-demo-all-in-one
- spec:
- type: LoadBalancer
- ports:
- - name: "http-port"
- protocol: "TCP"
- port: 8080
- targetPort: 8080
- selector:
- role: all-in-one
今回の場合だと、localhostを指定して接続するようにしています。
アプリケーションコンテナから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で指定します。
それでは、Kubernetesで作ったマニフェスト使ってデプロイしていきます。まず最初にKubernetesのノードと、その上で動いているPodを確認してみます。
- $ kubectl get nodes
- NAME STATUS ROLES AGE VERSION
- gke-flexy-demo-default-pool-5d473a6b-3pz7 Ready 8m28s v1.15.4-gke.22
- gke-flexy-demo-default-pool-5d473a6b-h3s5 Ready 8m22s v1.15.4-gke.22
- gke-flexy-demo-default-pool-5d473a6b-l580 Ready 8m28s v1.15.4-gke.22
- $ kubectl get pods
- No resources found in default namespace.
この段階ではまだPodはありません。
いよいよ作成したマニフェストをKubernetesに登録します。
- $ kubectl apply -f 1_pod.yaml
- configmap/init-db-sql created
- pod/sample-pod created
- service/flexy-demo-all-in-one created
- $ kubectl get services
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- flexy-demo-all-in-one LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:31945/TCP 2m45s
- kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 12m
- $ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- 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種類のランダムなユーザーが生成されていることが確認できます。
- $ for i in `seq 1 100`; do curl -s http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
- sample-pod: name is [1 Gorillascythe]
- sample-pod: name is [2 Cubrowan]
- sample-pod: name is [3 Chintabby]
- sample-pod: name is [4 Cloudbutter]
- sample-pod: name is [5 Samurailily]
ステップ1の実験が終わったら一旦不要なものは削除します。
- $ kubectl delete -f 1_pod.yaml
- configmap "init-db-sql" deleted
- pod "sample-pod" deleted
- service "flexy-demo-all-in-one" deleted
ここまでが最小構成のものです。
Deploymentでスケーリング
Podが1個だけでは何もうまみが感じられません。スケーリングさせることにより、利便性が出てきます。その際、アプリケーションとデータベースの機能の両方ともをスケールさせる機会は少ないでしょう。アプリケーションはいくつ、データベースはいくつと、それぞれ分割して考えていくことでしょう。そのため、アプリケーションとデータベースのコンテナは別々のPodにするべきです。
設定をマニフェストに落とし込むとこのようになります。
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: flexy-demo-app
- spec:
- replicas: 1
- selector:
- matchLabels:
- role: app
- template:
- metadata:
- labels:
- role: app
- spec:
- containers:
- - name: app-container
- image: masayaaoyama/flexy-demo-app:v1.0
- imagePullPolicy: Always
- env:
- - name: DBHOST
- value: mysql.default.svc.cluster.local
- - name: DBPORT
- value: "3306"
- - name: DBUSER
- value: myuser
- - name: DBPASS
- value: mypass
- - name: DBNAME
- value: mydb
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: flexy-demo-app
- spec:
- type: LoadBalancer
- ports:
- - name: "http-port"
- protocol: "TCP"
- port: 8080
- targetPort: 8080
- selector:
- role: app
- ---
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: init-db-sql
- data:
- create_usertable.sql: |
- CREATE TABLE IF NOT EXISTS mydb.users (id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(50));
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: mysql
- spec:
- replicas: 1
- selector:
- matchLabels:
- role: db
- template:
- metadata:
- labels:
- role: db
- spec:
- containers:
- - name: mysql-container
- image: mysql:8.0
- env:
- - name: MYSQL_ROOT_PASSWORD
- value: rootpass
- - name: MYSQL_DATABASE
- value: mydb
- - name: MYSQL_USER
- value: myuser
- - name: MYSQL_PASSWORD
- value: mypass
- volumeMounts:
- - name: init-sql-configmap
- mountPath: /docker-entrypoint-initdb.d
- volumes:
- - name: init-sql-configmap
- configMap:
- name: init-db-sql
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: mysql
- spec:
- type: ClusterIP
- ports:
- - name: "mysql-port"
- protocol: "TCP"
- port: 3306
- targetPort: 3306
- selector:
- role: db
よく見ると先ほどのものと書いてある内容は大きく変わっておらず、アプリケーションとデータベースがそれぞれ分割されて細かく構造化されていったものです。
また、先程はPodだったものが、Deploymentに変わっています。このDeploymentは、templateで指定されたPodの構成を複製するための機能になります。VMの場合は、インスタンスグループに近いイメージですね。Deploymentでは、レプリカの数を指定したり下記の図のようにどのPodをカウントするのかをselectorで指定します。templateにはPodを起動するときにrole:appラベルが付与されて起動してくるように指定されているので正しくカウントできるようになっています。
さてここで懸念点が発生します。アプリケーション用のコンテナから、データベース用のコンテナに対してはどのように接続したら良いのでしょうか。
最初の段階では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 と続きます。
では実際に試してみましょう。
- $ kubectl apply -f 2_deployment.yaml
- deployment.apps/flexy-demo-app created
- service/flexy-demo-app created
- configmap/init-db-sql created
- deployment.apps/mysql created
- service/mysql created
- $ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 8m36s
- mysql-5dd5b5c644-vtsjp 1/1 Running 0 8m35s
- $ kubectl get service
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- flexy-demo-app LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:32591/TCP 8m37s
- kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 24m
- mysql ClusterIP XXX.XXX.XXX.XXX 3306/TCP 8m37s
このように2つのPodとServiceが立ち上がります。
今回1つのPodに対して1つのコンテナしか入ってないので「1/1」の表示ですが、先程のようにPodの中に複数のコンテナがあればここの表示が「2/2」になります。
アプリケーション宛のロードバランサーは外部向けのIPアドレスが払い出さるので、ここにアクセスすると先ほど同様にアプケーションが動作します。
先程はブラウザでやりましたが、もちろん curlコマンドを使ってもuserの登録は可能です。
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-md6zr: added user [Houndbird]
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-md6zr: added user [Stallionsticky]
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-md6zr: added user [Racernimble]
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-md6zr: added user [Salmonautumn]
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-md6zr: added user [Fingeode]
- $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
- flexy-demo-app-5cb4bb64cd-md6zr: name is [1 Houndbird]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [2 Stallionsticky]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [3 Racernimble]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [4 Salmonautumn]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [5 Fingeode]
Deploymentはレプリカ数をスケールさせることも可能です。
例えば、レプリカを3つにするには次のようなコマンドを実行しましょう。データベースは1つのままで、アプリケーションだけが3つになったことが確認できます。
- $ kubectl scale deployment flexy-demo-app --replicas 3
- deployment.extensions/flexy-demo-app scaled
- $ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- flexy-demo-app-5cb4bb64cd-64t56 1/1 Running 0 47s
- flexy-demo-app-5cb4bb64cd-hphvf 1/1 Running 0 47s
- flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 10m
- mysql-5dd5b5c644-vtsjp 1/1 Running 0 10m
また同様にアプリケーションに対してリクエストを送ってみると、アプリケーションのホスト名も出力されているため、3*5 個の出力結果が確認できるかと思います。
- $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
- flexy-demo-app-5cb4bb64cd-64t56: name is [1 Houndbird]
- flexy-demo-app-5cb4bb64cd-64t56: name is [2 Stallionsticky]
- flexy-demo-app-5cb4bb64cd-64t56: name is [3 Racernimble]
- flexy-demo-app-5cb4bb64cd-64t56: name is [4 Salmonautumn]
- flexy-demo-app-5cb4bb64cd-64t56: name is [5 Fingeode]
- flexy-demo-app-5cb4bb64cd-hphvf: name is [1 Houndbird]
- flexy-demo-app-5cb4bb64cd-hphvf: name is [2 Stallionsticky]
- flexy-demo-app-5cb4bb64cd-hphvf: name is [3 Racernimble]
- flexy-demo-app-5cb4bb64cd-hphvf: name is [4 Salmonautumn]
- flexy-demo-app-5cb4bb64cd-hphvf: name is [5 Fingeode]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [1 Houndbird]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [2 Stallionsticky]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [3 Racernimble]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [4 Salmonautumn]
- flexy-demo-app-5cb4bb64cd-md6zr: name is [5 Fingeode]
図で表すと3つのPodが1つのMySQLを見に行くのでこのような状態です。
この状態では実際のサービスとしてはまだ不完全です。MySQLのPodを消した場合、データは消えてしまいます。試しに一度MySQLのPodを削除してみましょう。自動的に新しいMySQLのPodは立ち上がってくるため、データベース自体は存在しています。しかし、ユーザーの一覧を取得しようとしても、データが全て消えてしまっていることが確認できるかと思います。
- $ kubectl delete pods mysql-5dd5b5c644-vtsjp
- pod "mysql-5dd5b5c644-vtsjp" deleted
- $ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- flexy-demo-app-5cb4bb64cd-64t56 1/1 Running 0 10m
- flexy-demo-app-5cb4bb64cd-hphvf 1/1 Running 0 10m
- flexy-demo-app-5cb4bb64cd-md6zr 1/1 Running 0 20m
- mysql-5dd5b5c644-hiskx 1/1 Running 0 1m
- $ curl http://XXX.XXX.XXX.XXX:8080/getuser;
- curl: (52) Empty reply from server
この問題を解決するため、次はデータの永続化を行っていきましょう。その前に、このステップで作成したものを下記のコマンドを用いて削除しておきましょう。
- $ kubectl delete -f 2_deployment.yaml
- deployment.apps "flexy-demo-app" deleted
- service "flexy-demo-app" deleted
- configmap "init-db-sql" deleted
- deployment.apps "mysql" deleted
- service "mysql" deleted
Statefulで永続化
構成ファイルはほとんど同じですが、kindをStatefulSetにします。そのほかに、永続化Volumeの指定とマウント先の指定が必要になります。
ほとんどのKubernetes環境では、StatefulSetを使うことでPodを作る時にネットワーク越しに存在する永続化ボリュームを切り出してPodにアタッチしてくれます。今回はGKEを利用しているため、GCPのPersistentDiskから払い出されたディスクがPodの /var/lib/mysql にマウントされるような設定を行っています。
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: mysql
- spec:
- replicas: 1
- serviceName: mysql
- selector:
- matchLabels:
- role: db
- template:
- metadata:
- labels:
- role: db
- spec:
- containers:
- - name: mysql-container
- image: mysql:8.0
- env:
- - name: MYSQL_ROOT_PASSWORD
- value: rootpass
- - name: MYSQL_DATABASE
- value: mydb
- - name: MYSQL_USER
- value: myuser
- - name: MYSQL_PASSWORD
- value: mypass
- volumeMounts:
- - name: init-sql-configmap
- mountPath: /docker-entrypoint-initdb.d
- - name: datadir
- mountPath: /var/lib/mysql
- volumes:
- - name: init-sql-configmap
- configMap:
- name: init-db-sql
- volumeClaimTemplates:
- - metadata:
- name: datadir
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 5G
それでは実際にこのマニフェストを登録してみましょう。
- $ kubectl apply -f 3_statefulset.yaml
- deployment.apps/flexy-demo-app created
- service/flexy-demo-app created
- configmap/init-db-sql created
- statefulset.apps/mysql created
- service/mysql created
- kubectl get pods
- NAME READY STATUS RESTARTS AGE
- flexy-demo-app-5cb4bb64cd-qxmwc 1/1 Running 0 14m
- mysql-0 1/1 Running 0 14m
- $ kubectl scale deployment flexy-demo-app --replicas 3
- deployment.extensions/flexy-demo-app scaled
- $ kubectl get pods
- NAME READY STATUS RESTARTS AGE
- flexy-demo-app-5cb4bb64cd-2hgbv 1/1 Running 0 11s
- flexy-demo-app-5cb4bb64cd-dwkgd 1/1 Running 0 11s
- flexy-demo-app-5cb4bb64cd-qxmwc 1/1 Running 0 14m
- mysql-0 1/1 Running 0 14m
- $ kubectl get svc
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- flexy-demo-app LoadBalancer XXX.XXX.XXX.XXX XXX.XXX.XXX.XXX 8080:31831/TCP 14m
- kubernetes ClusterIP XXX.XXX.XXX.XXX 443/TCP 43m
- mysql ClusterIP XXX.XXX.XXX.XXX 3306/TCP 14m
先ほどまでは mysql−乱数 となっていたPod名が mysql-0 になっています。StatefulSetをスケールアウトさせると mysql-1 のようにカウントアップして増えていきます。逆にデータベースをスケールインする時には後ろの数が大きいものから消えていきます。このように、0は最初に作られて最後に消されるので、これをマスターにし、1以降をスレーブにするといった使い方が可能です。
それでは先程と同様の手順でにMySQL Podを停止してデータがそのままになるか試してみましょう。
- $ curl http://XXX.XXX.XXX.XXX:8080/adduser
- flexy-demo-app-5cb4bb64cd-dwkgd: added user [Spriteriver]
- $ curl http://XXX.XXX.XXX.XXX :8080/adduser
- flexy-demo-app-5cb4bb64cd-qxmwc: added user [Handpuddle]
- $ curl http://XXX.XXX.XXX.XXX :8080/adduser
- flexy-demo-app-5cb4bb64cd-dwkgd: added user [Wingruby]
- $ curl http://XXX.XXX.XXX.XXX :8080/adduser
- flexy-demo-app-5cb4bb64cd-dwkgd: added user [Ferretbattle]
- $ curl http://XXX.XXX.XXX.XXX :8080/adduser
- flexy-demo-app-5cb4bb64cd-dwkgd: added user [Maskroot]
- $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [5 Maskroot]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [5 Maskroot]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [5 Maskroot]
- $ kubectl delete pods mysql-0
- pod "mysql-0" deleted
- $ for i in `seq 1 100`; do curl -s curl http://XXX.XXX.XXX.XXX:8080/getuser; done | sort | uniq
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-2hgbv: name is [5 Maskroot]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-dwkgd: name is [5 Maskroot]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [1 Spriteriver]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [2 Handpuddle]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [3 Wingruby]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [4 Ferretbattle]
- flexy-demo-app-5cb4bb64cd-qxmwc: name is [5 Maskroot]
今回は正しくデータの永続化ができていますね。ここまでくると、ようやくよくある2層の構成ができました。今回のMySQLはクラスタ化していませんが、MySQLもクラスタ化するようにしておくことで、データベースのStatefulSetもスケールさせることができるようになります。
Ingressでルーティング
最後に、Ingressについて紹介します。これは、Service を束ねる機能です。簡単に言えば「リバプロ的な動きをするもの」で、アクセスを受けたホスト名やパスに対してどういうルーティングをするか指定できます。他にもKubernetesのSSL終端を任せることもできます。また、証明書をマニフェストとして登録しておくことも可能なため、証明書の交換も簡単に行うことが可能です。
下記の図だと productやuserの単位でマイクロサービスとしてどんどん横に増えていくイメージです。
ここまで来たら、マイクロサービスの土台ができたと言えるでしょう。
Kubernetesで効率的なサービス運用を!
今回は基本的な部分のみの紹介になりましたが、サービスを作っていく土台はできました。Kubernetesを駆使すればまだまだ便利で効率的なサービス運用ができるようになります。
いろいろと手を動かしながらKubernetesの素晴らしさを実感していってください。また、下記に様々なサンプルマニフェストも公開しているので、ぜひ利用してみてください。
https://github.com/MasayaAoyama/kubernetes-perfect-guide
■FLEXYのご紹介
このサイトを運営しているFLEXYは、エンジニア、技術顧問、CTO、デザイナーの方向けに案件をご紹介するサービスです。
リモートワークや週1-5日、高単価案件など、ご希望に合った案件をご紹介しています。お仕事をお探しの方は、是非お気軽にご相談ください。