元営業WEBエンジニアのアプリ開発日記

営業出身のWEB系エンジニアが気になったものから作ってはメモを残してくブログ

kubernetesでローカル環境にNGINX, golang, MySQL環境作ってみる

概要

k8sのチュートリアルやってみたし自分用のアプリケーション作ってみる!
こんな感じの構成にしたい。DB接続までやってみよう。

  • フロント
    • NGINX
    • (React)
    • (TypeScript)
  • バックエンド

構築

クラスタ作成

k8sチュートリアル通りminikube使ってローカル環境にクラスタ作る。
ちなみにこれ基本Mac想定

$ minikube stop
$ # deleteしとかないとちょくちょく起動しないときある
$ minikube delete
$ # virtualbox重いのでhyperkitで行く
$ minikube start --vm-driver=hyperkit

フロントエンド

とりあえずNginx起動させてWelcome to nginx!表示させたい

Dockerファイルを作成する

Dockerfile

FROM nginx:1.15.8
LABEL maintainer="shintaro.0112@gmial.com"
EXPOSE 80

COPY ./conf/nginx.conf /etc/nginx/conf.d/nginx.conf

./conf/nginx.conf

server {
    listen 8080;
    server_name nginx.ucwork.local;

    access_log /var/log/nginx/ucwork_access.log;
    error_log /var/log/nginx/ucwork_error.log;

    location / {
       root   /usr/share/nginx/html;
       index  index.html index.htm;
    }
}

Docker Hubにレポジトリ登録

Docker Hubでアカウント登録し、ローカルでログインしとく。

$ docker login
$ # 登録したusername
$ # passwordを打ち込む

commitしてpush

$ docker build -t shintaro0123/nginx:1.0.0 .
$ docker run -d -p 8080:8080 --name my-nginx-app shintaro0123/nginx:1.0.0
$ docker commit -m "何かしらのコメント" my-nginx-app shintaro0123/nginx:1.0.0
$ docker push shintaro0123/nginx:1.0.0

deployment, pod, service, pvc作成

nginx-deployment.yamlはこんな感じにする

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 8080
  selector:
    app: nginx
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nx-pv-claim
  labels:
    app: nginx
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nginx
        tier: frontend
    spec:
      containers:
      - image: shintaro0123/nginx:1.0.0
        name: nginx
        ports:
        - containerPort: 8080
          name: nginx
        volumeMounts:
        - name: nginx-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: nginx-persistent-storage
        persistentVolumeClaim:
          claimName: nx-pv-claim

全部立ててみよう!

$ kubectl create -f nginx-deployment.yaml
$ # 一度作成して何か修正したらcreateじゃなくてapply使うっぽい
$ # kubectl apply -f nginx-deployment.yaml

Ingress作成

podにアクセスするためにはserviceを経由するが、service経由すると都度公開されるポートが変わったりで大変そうなので、IngressとIngressControllerなるものを使う。
アプリケーションレベルでロードバランシングするL7LBっつう奴らしい。

$ minikube addons list
$ # ingressの有効化
$ minikube addons enable ingress

ingressの宣言ingress.yamlを作成。hostの名前は自分の好きにして良い
ingressが効いてるの確認するために80->8080ポートにアクセスさせてる

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ucwork.local
  annotations:
    nginx.org/server-snippet: "proxy_ssl_verify off;"
spec:
  rules:
  - host: nginx.ucwork.local
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: 8080
  - host: go.ucwork.local
    http:
      paths:
      - backend:
          serviceName: go
          servicePort: 8080

実際にingress作成

$ kubectl create -f ingress/ingress.yaml
$ # 一度作成して何か修正したらcreateじゃなくてapply使うっぽい
$ # kubectl apply -f ingress/ingress.yaml

動作確認

$ # 各種起動状態の確認
$ minikube addons list
$ # dashboardの有効化
$ minikube addons enable dashboard
$ # ブラウザが開いて各種状態がわかるページ出てくる
$ minikube dashboard
$ # コマンド叩いて確認してもいい
$ kubectl get all
$
$ # httpでアクセスしてWelcome to nginx!出れば素敵!
$ curl  http://`minikube ip`/ -H 'Host: nginx.ucwork.local'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

バックエンド

MySQL

Secret作成

パスワードとか公開しちゃいけないものをいい感じで使えるSecret利用
yourpassword部分を自分の好きなパスワードにする

kubectl create secret generic mysql-pass --from-literal=password=yourpassword

Dockerファイル作成

Dockerfile

FROM mysql:8.0.15
LABEL maintainer="shintaro.0112@gmial.com"

# 設定ファイルを配置
# (MySQLは設定ファイルの権限が777だと読み込まない)
COPY ./conf/charset.cnf /etc/mysql/conf.d/charset.cnf
RUN chmod 644 /etc/mysql/conf.d/*

RUN apt-get update && \
  apt-get install -y locales && \
  rm -rf /var/lib/apt/lists/* && \
  echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
  locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8

./conf/charset.cnf

[mysqld]
explicit_defaults_for_timestamp = 1
character-set-server=utf8
sql_mode=NO_ENGINE_SUBSTITUTION
[mysql]
default-character-set=utf8

Docker Hubレポジトリにpush

commitしてpush

$ docker build -t shintaro0123/mysql:1.0.0 .
$ docker run -d --name my-mysql-app shintaro0123/mysql:1.0.0
$ docker commit -m "何かしらのコメント" my-mysql-app shintaro0123/mysql:1.0.0

deployment, pod, service, pvc作成

mysql-deployment.yamlはこんな感じ

apiVersion: v1
kind: Service
metadata:
  name: go-mysql
  labels:
    app: go
spec:
  ports:
    - port: 3306
  selector:
    app: go
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: go
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: go-mysql
  labels:
    app: go
spec:
  selector:
    matchLabels:
      app: go
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: go
        tier: mysql
    spec:
      containers:
      - image: shintaro0123/mysql:1.0.0
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

全部立ててみーる

$ kubectl create -f mysql-deployment.yaml
$ # 一度作成して何か修正したらcreateじゃなくてapply使うっぽい
$ # kubectl apply -f mysql-deployment.yaml

動作確認

MySQLに接続できることを確認。パスワードはSecretで設定したものを入れる

$ # podの名前を確認
$ kubectl get pods
$ # 確認したpod NAME
$ kubectl exec -it [pod NAME] bash
root@go-mysql-7469cd46ff-5xdwh:/# mysql -u root -p
Enter password:
mysql> # データベースとテーブル作ってデータ入れとく
mysql> create database ucwork;
Query OK, 1 row affected (0.01 sec)
mysql> use ucwork
Database changed
mysql> create table users(id int AUTO_INCREMENT NOT NULL PRIMARY KEY, name varchar(255) not null);
Query OK, 0 rows affected (0.03 sec)
mysql>
mysql> insert into users values(1, "taro");
Query OK, 1 row affected (0.02 sec)

golang

goアプリケーションの作成

go全然書いたこと無いんでとりあえず、httpリクエスト受けて、MySQLからなんか取ってくるところを書く。とにかく動けばいいの精神レベル(これからちゃんと勉強したいな・・・)
main.go

package main

import (
    "database/sql"
    "fmt"
    "net/http"
    "os"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    /** URLパス表示 */
    w.Write([]byte("url path is " + r.URL.Path[1:] + "\n"))

    /** DB接続 */
    var dbConnectQuery string
    dbConnectQuery = "root:" + os.Getenv("GO_DB_PASSWORD") + "@tcp(" + os.Getenv("GO_DB_HOST") + ":3306)/ucwork"
    db, err := sql.Open("mysql", dbConnectQuery)
    if err != nil {
        panic(err.Error())
    }
    defer db.Close() // 関数がリターンする直前に呼び出される

    rows, err := db.Query("SELECT * FROM users") //
    if err != nil {
        panic(err.Error())
    }

    columns, err := rows.Columns() // カラム名を取得
    if err != nil {
        panic(err.Error())
    }

    values := make([]sql.RawBytes, len(columns))

    scanArgs := make([]interface{}, len(values))
    for i := range values {
        scanArgs[i] = &values[i]
    }

    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            panic(err.Error())
        }

        var value string
        for i, col := range values {
            // Here we can check if the value is nil (NULL value)
            if col == nil {
                value = "NULL"
            } else {
                value = string(col)
            }
            w.Write([]byte(columns[i] + ": " + value + "\n"))
        }
        fmt.Println("-----------------------------------")
    }
}

goインストールしてビルドじゃ!!コンテナ用にクロスコンパイルする

$ env GOOS=linux GOARCH=amd64 go build main.go

Dockerファイル作成

コンパイルして出力されたmainファイルをコンテナに渡して起動させる
Dockerfile

FROM golang:1.11.5
LABEL maintainer="shintaro.0112@gmial.com"
EXPOSE 8080

WORKDIR /go/src/app
RUN go get github.com/go-sql-driver/mysql
COPY main ./

CMD ["./main"]

Docker Hubレポジトリにpush

buildしてrunしてcommitしてpush

$ docker build -t shintaro0123/golang:1.0.0 .
$ docker run -d --name my-golang-app shintaro0123/golang:1.0.0
$ docker commit -m "何かしらのコメント" my-golang-app shintaro0123/golang:1.0.0

deployment, pod, service, pvc作成

go-deployment.yamlこんな感じで作った

apiVersion: v1
kind: Service
metadata:
  name: go
  labels:
    app: go
spec:
  ports:
    - port: 8080
  selector:
    app: go
    tier: backend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: go-pv-claim
  labels:
    app: go
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: go
  labels:
    app: go
spec:
  selector:
    matchLabels:
      app: go
      tier: backend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: go
        tier: backend
    spec:
      containers:
      - image: shintaro0123/golang:1.0.0
        name: go
        env:
        - name: GO_DB_HOST
          value: go-mysql
        - name: GO_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 8080 
          name: go
        volumeMounts:
        - name: go-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: go-persistent-storage
        persistentVolumeClaim:
          claimName: go-pv-claim

動作確認

ingressで設定したホスト名でアクセスするとよろしくgo serviceにアクセスされる
URLのパスとデータベースに格納された値一覧が表示される。

$ curl  http://`minikube ip`/abcd -H 'Host: go.ucwork.local'
$ # ブラウザから見たい場合は minikube ipコマンドで出力されたipとhost名をhostsに指定

まとめ

あとはNGINXのコンテナにReactとTypeScriptでアプリケーション作ってgolangAPIリクエスト投げる感じにしたいなぁ

webでアクセスしてDBから情報取ってくるだけなのに結構いろんなとこで詰まった・・
k8s自体は奥が深そうなので、都度都度学習していき学んでいこう。。

TODO

  • MySQLDDL,SQLを起動時に自動実行
  • DockerHubとgithunの連携
  • nghttpxによるhttp/2対応
    • gRPCやりたい
  • docker commit、push、デプロイの自動化。Skaffold?
  • そもそもgolangの勉強

参考にさせてもらったサイト