ローカル環境 kubernetesでWebアプリ起動
kubernetesの基礎習得のため、Webアプリの起動にトライした記事になります。
goで作成したWebアプリをkubernetesクラスタで起動し、DBのデータの追加・参照できるようににしていきます。
環境
OS: Windows10 docker: 20.10.10 kind: 0.17.0
事前準備
kubernetesクラスタの準備とDBを起動します。お試しなので全てローカルで起動します。クラウドを使用した場合料金がかかるので、一旦試してみたい方はこの方法で試すと良いかなと思います。
クラスタ起動
kind を使用し、Dockerコンテナでマルチノードのクラスタを起動します。プライベートなコンテナレジストリからイメージを取得するため、認証情報(deploy token)をsecret.json経由で与えています。
kind-cluster.yaml
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: webapp-cluster nodes: - role: control-plane extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json - role: worker extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: ./secret.json
secret.json
{ "auths": { "registry.gitlab.com": { "auth": "****" } } }
$ kind create cluster --config kind-cluster.yaml $ docker ps --format "{{.ID}}\t{{.Image}}\t{{.Names}}" 46911f618580 kindest/node:v1.25.3 webapp-cluster-control-plane 1bcddfaf552f kindest/node:v1.25.3 webapp-cluster-worker aac1c231d95d kindest/node:v1.25.3 webapp-cluster-worker2
クラスタを起動すると、1つのマスタ(control-plane)と2つのワーカ(woker)コンテナが起動していることが確認できます。
DB起動
kindで起動すると3つのコンテナが作成され、kind
というDockerネットワークが作成されます。このネットワーク内に、DBコンテナを起動していきます。DBはPostgres14を使用します。
$ docker network ls NETWORK ID NAME DRIVER SCOPE d67081ee3cc5 bridge bridge local d705790474c7 host host local 62f01985424e kind bridge local ⇐ これ 0d2e79587807 none null local # DBコンテナ起動 # --nat=kind ネットワーク指定 # -e POSTGRES_PASSWORD=password パスワードを環境変数で設定 $ docker run --name db -e POSTGRES_PASSWORD=password -d --net kind postgres:14 $ docker ps --format "{{.ID}}\t{{.Image}}\t{{.Names}}" 6aaa1e918b9f postgres:14 db 46911f618580 kindest/node:v1.25.3 webapp-cluster-control-plane 1bcddfaf552f kindest/node:v1.25.3 webapp-cluster-worker aac1c231d95d kindest/node:v1.25.3 webapp-cluster-worker2
DB、テーブル作成
コンテナへ接続し、psqlコマンドで直接作成していきます。
# コンテナ接続 $ docker exec -it <コンテナID> bash # Postgres DB接続 $ psql -U postgres # DB作成 create database sample; # DB切り替え \c sample # テーブル作成 create table users( id serial, name varchar );
Webアプリの作成
golang echoフレームワークでuserの作成、参照を行えるエンドポイントを定義します。
- GET: /users/:id パスで指定されたidのユーザを返す
- POST: /users ユーザを1件追加し、追加されたユーザを返す
src/server.go
package main import ( "net/http" "github.com/labstack/echo/v4" "gitlab.com/test/kubernetes-web-app/src/handler" ) func main() { handler.Init() e := echo.New() e.GET("/users/:id", handler.GetUser()) e.POST("/users", handler.AddUser()) e.Logger.Fatal(e.Start(":1323")) }
src/handler/user.go
package handler import ( "database/sql" "fmt" "net/http" "os" "github.com/labstack/echo/v4" _ "github.com/lib/pq" ) type User struct { Id int `json:"id"` Name string `json:"name"` } var content string var Db *sql.DB func Init() { var err error Db, err = sql.Open( "postgres", fmt.Sprintf( "host=%s port=%s user=%s password=%s dbname=sample sslmode=disable", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), ), ) if err != nil { panic(err) } } func GetUser() echo.HandlerFunc { return func(c echo.Context) error { id := c.Param("id") user := User{} if err := Db.QueryRow("SELECT id, name FROM users where id = $1", id).Scan(&user.Id, &user.Name); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } return c.JSON(http.StatusOK, user) } } func AddUser() echo.HandlerFunc { return func(c echo.Context) error { name := c.FormValue("name") newId := 0 if err := Db.QueryRow("INSERT INTO users(name) VALUES($1) RETURNING id", name).Scan(&newId); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } user := User{} if err := Db.QueryRow("SELECT id, name FROM users where id = $1", newId).Scan(&user.Id, &user.Name); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } return c.JSON(http.StatusOK, user) } }
Dockerイメージの作成
作成したアプリをDockerイメージ化して、コンテナレジストリへ追加します。リポジトリへPushしたときに自動で実行されるようGitlab-runner設定と、.gitlab-ci.ymlファイルでそのコマンドを記載しています。ただこのファイルでは Dockerイメージにlatestタグしか付かず、上書きされる形になるので、ビルドイメージごとにタグを切り替えていけるよう修正が必要です。
Dockerfile
apiVersion: v1 kind: Service metadata: name: webapp-service spec: ports: - port: 1323 targetPort: 1323 protocol: TCP name: http selector: app: webapp type: LoadBalancer
.gitlab-ci.yml
stages: - build image-build: stage: build tags: - docker image: docker:latest services: - docker:dind script: - docker info - docker build -t ${CI_REGISTRY}/test/kubernetes-web-app . - docker tag ${CI_REGISTRY}/test/kubernetes-web-app ${CI_REGISTRY}/test4038/kubernetes-web-app:latest - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker image push --all-tags ${CI_REGISTRY}/test/kubernetes-web-app
kubernetes マニフェスト作成、起動
Webアプリをdeploymentで起動し、そのPodへのアクセスを分散するめLoadbalancerを起動します。DBの接続情報であるuser/paswordはコードに直接記載したくないので、Secretを作成しそこから取得できるようにしています。
deployment, service
apiVersion: apps/v1 kind: Deployment metadata: name: webapp-deployment labels: app: webapp spec: replicas: 3 selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - name: webapp image: registry.gitlab.com/test4038/kubernetes-web-app:latest ports: - containerPort: 1323 env: - name: DB_HOST value: db - name: DB_PORT value: "5432" - name: DB_USER valueFrom: secretKeyRef: name: webapp-secret key: user - name: DB_PASSWORD valueFrom: secretKeyRef: name: webapp-secret key: password --- apiVersion: v1 kind: Service metadata: name: webapp-service spec: ports: - port: 1323 targetPort: 1323 protocol: TCP name: http selector: app: webapp type: LoadBalancer
secret
.data
に key : value
の形で複数の値を設定できます。値はbase64でエンコードしてから設定する必要があります。
apiVersion: v1 kind: Secret metadata: name: webapp-secret type: Opaque data: user: cG9zdGdyZXMK # postgres password: cGFzc3dvcmQK # epassword
マニフェストが作成できたので、kubernetesクラスタでアプリを起動していきます。
# Secret作成 $ kubectl apply -f secret.yaml # マニフェストから起動 $ kubectl apply -f deployment.yaml $ kubectl apply -f service.yaml
動作確認
curlコマンドでHTTPリクエストし、userの追加、参照が出来ることを確認しています。
# ポートフォワーディング $ kubectl port-forward service/webapp-service 1323:1323 # アクセス確認 # POST user作成 $ curl -X POST localhost:1323/users -d 'name=hoge' {"id":1,"name":"hoge"} # GET user参照 $ curl localhost:1323/users/1 {"id":1,"name":"hoge"}