k3dとskaffoldで始めるKubernetesローカル開発

2021-03-15

GhinzoによるPixabayからの画像

#要約

#はじめに

Kubernetes使ってますか?
コンテナオーケストレーションのデファクトスタンダードと言われて久しいですが、私が業務で関わる案件は大体ECSが使われています(汗)
自社開発ではKubernetes一択なのですが、なぜコンテナオーケストレーションとして、なぜKubernetesを選ぶかというと以下が理由です。

ローカルでクラスターを構築できるという点が便利だなと思います。
素のKubernetesクラスターを立てるのは大変なのですが、次のツールなどが出揃っていて、LaptopやDesktopでサクッとクラスターを構築できることが魅力です。これは、ローカル開発においてローカルのk8s cluster上にサービスをデプロイして本番に近しい構成でテストをすることができます。Cloudを使わなくてもテストができるので、結合テストやE2Eの自動化がやりやすくなりますし、開発スピードを向上できることが気に入っています。

#Kubernetesディストリビューション

ローカル開発向けで代表的な Kubernetesディストリビューションを列挙します

他にもあると思いますが、私が試したことがあるのが上記です。
気に入ったものを使っていけば良いと思いますが、
今回の記事では、k3d を使っていこうと思います。
VMベースのものより、コンテナーベースのほうが軽量でストレスが少ないかなと思いました。
k3dは、Docker上にk3sのServer, Agentが動くので、Clusterを作ったり破棄したりするのが簡単にできて便利だと思います。ベースになっているk3sはCNCFがホストしていますので、普及・成長を期待しています。
k8s clusterのネットワーク上にRegistryのDockerコンテナを動作させて、ローカルでイメージをプッシュすれば、Clusterから参照することができます。これで、一通りローカルでデプロイまで完結することができます。

#前提

この記事の全コードは
GitHub
を参照ください。
CloudShellで試すこともできます。

Open in Cloud Shell

#workspace directory

mkdir -p ~/workspace/project
cd ~/workspace/project

#k3d install

k3dのGitHubに記載されている通り、Installを行います。
この記事を作成時点では、v4.2.0です。

curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash

k3dがCLIで利用できることを確認します

$ k3d version
k3d version v4.2.0
k3s version v1.20.2-k3s1 (default)

$ k3d
Usage:
  k3d [flags]
  k3d [command]

Available Commands:
  cluster     Manage cluster(s)
  completion  Generate completion scripts for [bash, zsh, powershell | psh]
  help        Help about any command
  image       Handle container images.
  kubeconfig  Manage kubeconfig(s)
  node        Manage node(s)
  version     Show k3d and default k3s version

Flags:
  -h, --help      help for k3d
      --verbose   Enable verbose output (debug logging)
      --version   Show k3d and default k3s version

Use "k3d [command] --help" for more information about a command.

#Makefileを作る

docker-compose likeにClusterのcreateができるようMakefileを作ります
簡単に説明するとcreateで下記のことを行います。downは逆のことを行います。

# ~/workspace/project/Makefile
# k3d, registry vars
CLUSTER_NAME=myk3dcluster
REGISTRY_IMAGE=registry:2
REGISTRY_HOST=registry.local
REGISTRY_PORT=5000
REGISTRY_VOLUME=local_registry_volume
DEFAULT_REPO="$(REGISTRY_HOST):$(REGISTRY_PORT)"

# for k3d cluster
.PHONY: create
create:
	k3d cluster create $(CLUSTER_NAME) \
		--volume "$(PWD)/registry/registries.yaml:/etc/rancher/k3s/registries.yaml"
	docker volume create $(REGISTRY_VOLUME)
	docker container run -d \
		--net k3d-$(CLUSTER_NAME) \
		--name $(REGISTRY_HOST) \
		-v $(REGISTRY_VOLUME):/var/lib/registry \
		--restart always \
		-p $(REGISTRY_PORT):$(REGISTRY_PORT) \
		$(REGISTRY_IMAGE)

.PHONY: start
start:
	k3d cluster start $(CLUSTER_NAME)

.PHONY: dev
dev:
	skaffold dev --profile dev --port-forward --default-repo $(DEFAULT_REPO)

.PHONY: down
down:
	k3d cluster stop $(CLUSTER_NAME)

.PHONY: destroy
destroy:
	docker stop $(REGISTRY_HOST)
	docker rm $(REGISTRY_HOST)
	docker volume rm $(REGISTRY_VOLUME)
	k3d cluster delete $(CLUSTER_NAME)

#registry config

k3dのprivate registryに関する公式ドキュメントに記載されている通り、registries.yamlを配置することでローカルに立てたRegistryにアクセスすることが可能になります。

mkdir -p ~/workspace/project/registry

# ~/workspace/project/registry/registries.yaml
mirrors:
  "registry.local:5000":
    endpoint:
      - http://registry.local:5000

#makeでclusterを作ったり破棄する

cluster及びregistryを生成します。

$ make create
k3d cluster create myk3dcluster \
        --volume "/home/sakurai/workspace/project/registry/registries.yaml:/etc/rancher/k3s/registries.yaml"
WARN[0000] No node filter specified                     
INFO[0000] Created network 'k3d-myk3dcluster'           
INFO[0000] Created volume 'k3d-myk3dcluster-images'     
INFO[0001] Creating node 'k3d-myk3dcluster-server-0'    
INFO[0003] Creating LoadBalancer 'k3d-myk3dcluster-serverlb' 
INFO[0010] Cluster 'myk3dcluster' created successfully! 
INFO[0010] You can now use it like this:                
kubectl cluster-info
docker volume create local_registry_volume
local_registry_volume
docker container run -d \
        --net k3d-myk3dcluster \
        --name registry.local \
        -v local_registry_volume:/var/lib/registry \
        --restart always \
        -p 5000:5000 \
        registry:2
7a343d43f1486db95b886a1985f83527dfea59e60bb25bb8da863dd3cee09dbe

docker psで確認します。

$ docker ps
CONTAINER ID   IMAGE                      COMMAND                  CREATED              STATUS              PORTS                              NAMES
7a343d43f148   registry:2                 "/entrypoint.sh /etc…"   About a minute ago   Up About a minute   5000/tcp, 0.0.0.0:5000->5000/tcp   registry.local
4b1d863e3581   rancher/k3d-proxy:v3.0.1   "/bin/sh -c nginx-pr…"   About a minute ago   Up About a minute   80/tcp, 0.0.0.0:45627->6443/tcp    k3d-myk3dcluster-serverlb
589456e30bac   rancher/k3s:v1.18.6-k3s1   "/bin/k3s server --t…"   2 minutes ago        Up About a minute                                      k3d-myk3dcluster-server-0

cluster生成時に自動的に..kube/configにアクセス情報が記載されるようです。
下記のコマンドでkubernetesにアクセスできるか確認します。

kubectl cluster-info

k3d containerのdocker stop が行われます。

$ make stop
k3d cluster stop myk3dcluster
INFO[0000] Stopping cluster 'myk3dcluster'              

k3d containerのdocker run が行われます。

$ make start
k3d cluster start myk3dcluster
INFO[0000] Starting cluster 'myk3dcluster'              
INFO[0000] Starting Node 'k3d-myk3dcluster-server-0' 

cluster及びregistryを削除します。

$ make down
k3d cluster stop myk3dcluster
INFO[0000] Stopping cluster 'myk3dcluster'              
docker stop registry.local
registry.local
docker rm registry.local
registry.local
docker volume rm local_registry_volume
local_registry_volume
k3d cluster delete myk3dcluster
INFO[0000] Deleting cluster 'myk3dcluster'              
INFO[0000] Deleted k3d-myk3dcluster-serverlb            
INFO[0000] Deleted k3d-myk3dcluster-server-0            
INFO[0000] Deleting cluster network '8bc0fecee2ca0660c549c7630a1689fc0f90e1082fedb1f60ae5895b655f9f6e' 
INFO[0000] Deleting image volume 'k3d-myk3dcluster-images' 
INFO[0000] Removing cluster details from default kubeconfig... 
INFO[0000] Removing standalone kubeconfig file (if there is one)... 
INFO[0000] Successfully deleted cluster myk3dcluster! 

#skaffold config

make dev が動作するようskaffoldを設定します。

apiVersion: skaffold/v2beta1
kind: Config
metadata:
  name: service_name
build:
  artifacts:
    - image: hello
      context: .
  tagPolicy:
    sha256: {}
  local:
    useBuildkit: true
deploy:
  kubectl:
    manifests:
      - manifests/hello.yaml

#k8s manifests

skaffoldのdeployでkubectlがapplyするmanifestを配置します。

apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  selector:
    app: hello
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hello
  name: hello
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - image: hello
          imagePullPolicy: "IfNotPresent"
          name: hello
          command:
            - "./server"
          ports:
            - containerPort: 8080
              protocol: TCP

#Hello, World Service

8080Hello, Worldを出力するHTTP ServerをGoで作ります。

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World")
}

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

Kubernetes上で動作するContainerImageを作ります。

FROM golang:1.15 as builder
WORKDIR /go/src/github.com/owner/repo
ENV GO111MODULE="on"
ENV CGO_ENABLED=0 
COPY . . 
RUN go build -o server main.go

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /go/src/github.com/owner/repo/server  /go/bin/server
WORKDIR /go/bin/

#install skaffold

curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \
sudo install skaffold /usr/local/bin/

#ローカル開発

これでプログラムの準備ができました。
make dev を実行します。
DEFAULT_REPOはcluster作成時に一緒に作成したregistryのホスト名に置き換わります。

$ make dev
skaffold dev --port-forward --default-repo "registry.local:5000"
Listing files to watch...
 - hello
Generating tags...
 - hello -> registry.local:5000/hello:latest
Checking cache...
 - hello: Found Locally
Tags used in deployment:
 - hello -> registry.local:5000/hello:df5353aa06239ed065f981abe3c1198ea7f2473694e6163f0256b44018c877eb
Loading images into k3d cluster nodes...
 - registry.local:5000/hello:df5353aa06239ed065f981abe3c1198ea7f2473694e6163f0256b44018c877eb -> Found
Images loaded in 113.175619ms
Starting deploy...
 - service/hello created
 - deployment.apps/hello created
Waiting for deployments to stabilize...
 - deployment/hello is ready.
Deployments stabilized in 3.433 seconds
Press Ctrl+C to exit
Watching for changes...
Port forwarding service/hello in namespace default, remote port 8080 -> address 127.0.0.1 port 8080

node.jsでvue.jsやreactで開発された経験のある方はnpm run devなどのコマンドでHotReloadを利用されているかと思います。skaffoldでもファイルの変更を検知して自動的にDocker build, kubectlでデプロイすることまでできます。port-forwardをしているので、portのバッティングなどがなければ、127.0.0.1:8080にアクセスするとHello, World Serviceにアクセスできると思います。
きちんと動作しているかcurlコマンドを叩いてみましょう。

$ curl localhost:8080
Hello, World

main.goHello, Worldの部分をHello, k8sの文字列に変えると再ビルド、デプロイが行われることを確認できます。また、curlコマンドを叩いてみましょう。

$ curl localhost:8080
Hello, k8s

#まとめ

必要最低限ですが、k3dとskaffoldを使ったローカル開発環境を構築しました。skaffoldをproduction,staging,develop環境でも使おうとするとprofileを分けたり、kustomizeやhelmを使って環境差異を埋めるようなことが必要になると思います。そのため、実用には遠いと思いますが、これからKubernetesでローカル開発環境を整えていこうという方にとって開発イメージがつかめて、一助になれば幸いです。


クラウドアンドビルド合同会社ではGoogle Cloudを使ったインフラ構築、システム開発、働き方改革ソリューションを提供しています。ご気軽に、ご相談・お問い合わせください。