kubernetesでwebアプリケーションを起動

kubernetes の基礎的な勉強の一環で、Webアプリケーションの構築からアクセスまでを実践しています。

構成

クラスタの構築にはkindを使用し、ローカル環境でDockerコンテナを使い3つのノードを起動します。(Dockerがインストールされていること)

アプリは Go の WebFramework Echo を使用して簡単なものを作成し、Deploymentで3つのPodを起動させます。
アクセスはLoadBalancerで各Podに分散するようになっています。

コードをGitLabで管理していたため、DockerイメージはGitLabのコンテナレジストリを使用しています。

Webアプリケーション作成

まずは直接ローカルでWebアプリケーションを起動して、動作確認してみます。
GETメソッドで2つエンドポイントを定義した簡単なものです。

  • / : HelloWorld を返す
  • /users : クエリパラメータで受け取った name, email 値を返す

main.go

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

type User struct {
    Name  string `json:"name" query:"name"`
    Email string `json:"email" query:"email"`
}

func main() {
    e := echo.New()

    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
    })

    e.GET("/users", func(c echo.Context) error {
        u := new(User)
        if err := c.Bind(u); err != nil {
            return err
        }
        return c.JSON(http.StatusOK, u)
    })

    e.Logger.Fatal(e.Start(":1323"))
}

echo.labstack.com

動作確認

2つのエンドポイントにアクセスし、レスポンスが返ってくることを確認

# アプリケーション起動
$ go run main.go

# アクセス
$ curl http://localhost:1323/
Hello, World!
$ curl http://localhost:1323/users?name=hoge&email=hoge@example.com
{"name":"hoge","email":"hoge@example.com"}

Dockerイメージ作成

kubernetesで起動させるためDockerイメージ化し、コンテナ上で起動できるか確認

Dockerfile

FROM golang:1.19.5-buster as build

WORKDIR /app
COPY go.mod ./
COPY go.sum ./
COPY server.go ./

RUN go mod download
RUN go build -o /webapp

FROM gcr.io/distroless/base-debian11

WORKDIR /

COPY --from=build /webapp /webapp

USER nonroot:nonroot

ENTRYPOINT ["/webapp"]

コンテナで起動して動作確認

同じくコンテナでもアクセスを確認

# イメージビルド
$ docker image build -t webapp .
# コンテナ起動
$ docker run -p 1323:1323 webapp

# アクセス
$ curl http://localhost:1323/
Hello, World!
$ curl http://localhost:1323/users?name=hoge&email=hoge@example.com
{"name":"hoge","email":"hoge@example.com"}

コンテナレジストリへDockerイメージのpush

GitLab Runner を使用して、パイプライン実行時にイメージのbuild,コンテナレジストリへのpush を行います
Runner は自前で起動せずGitLabのSharedRunnerを有効化し、共有されているものを使用します

.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}/kubernetes-web-app:${CI_COMMIT_SHA} . 
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker image push ${CI_REGISTRY}/kubernetes-web-app:${CI_COMMIT_SHA}

GitLab DeployTokenの発行

コンテナレジストリからDockerイメージを取得するため、DeployTokenを発行します
「Setting」⇒「Repository」⇒「Depoy tokens」

Pull an Image from a Private Registry | Kubernetes

kuberntesクラスタの起動

クラスタの定義はkind-cluster.yaml で定義し,Dockerコンテナで起動。GitLabのコンテナレジストリから、Dockerイメージを取得するためconfig.jsonをマウントしてクラスタに認証情報を与えます。

config.json

発行したDeployTokenを{username}:{password} の形式でbase64エンコードしたものをauthに使用します。
Scopeはread_registryが必要です。

# DeployToken base64エンコード
$ echo -n {username}:{password} | base64 
Z2l*******enZf

# config.jaon
{
    "auths": {
        "registry.gitlab.com": {
            "auth": "Z2l*******enZf"
        }
    }
}

kind-cluster.yaml

config.json を各ノードにマウントし、GitLabの認証情報を与えます

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

ノードの定義ファイルが作成できたら、クラスタを起動していきます

$ kind create cluster --config kind-cluster.yaml

Pod,Serviceリソースの作成, アクセス確認

Deployment, Service にマニフェストを作成し、クラスタ上で起動します。kindで作成されるクラスタでは、LoadBalancerを作成してもコンテナ外からアクセス出来ないため、一時的にポートフォワーディングを行いアクセスする。

deployment.yaml

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/kubernetes-web-app:latest
        ports:
        - containerPort: 1323

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
spec:
  ports:
  - port: 1323
    targetPort: 1323
    protocol: TCP
    name: http
  selector:
    app: webapp
  type: LoadBalancer
# リソース作成
$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml

# ポートフォワーディング
$ kubectl port-forward service/webapp-service 1323:1323

# アクセス
$ curl http://localhost:1323/
Hello, World!
$ curl http://localhost:1323/users?name=hoge&email=hoge@example.com
{"name":"hoge","email":"hoge@example.com"}