2023-07-02
はじめに
ローカル環境に立ち上げた Kubernetes クラスターの Pod にホスト側(ブラウザなど)からアクセスしたいとき kubectl port-forward
コマンドを実行しますが、リクエストを送信できる Pod が 1 つに決まってしまいます。
アプリケーションの動作確認なら良いのですが、リクエストが複数に分散できているか確認したい場合に不便です。
今回の記事では、ホスト側から Pod に curlできる環境を Kustomize、Helm 両方で作成します。
目標
Nginx のイメージを使って Pod を作成し、ホスト側から curl でlocalhost:8080
にリクエストできるようにする。
Pod のログを確認して、リクエストが振り分けられているか確認する。
構成図
バージョン
バージョン kind v0.19.0 kubectl gitVersion: v1.27.2 Kubernetes v1.27.1 Kustomize v5.1.0 Helm v3.8.0 stern 1.25.0
クラスターを作成する
まずはクラスターの準備をします。
kind で作成する Node は Docker コンテナなので、extraPortMappings
でポートを開ける必要があります。
apiVersion : kind.x-k8s.io/v1alpha4
kind : Cluster
name : sandbox
nodes :
- role : control-plane
image : kindest/node:v1.27.1
kubeadmConfigPatches :
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings :
- containerPort : 80
hostPort : 8080
- role : worker
image : kindest/node:v1.27.1
- role : worker
image : kindest/node:v1.27.1
クラスターを作成します。
kind create cluster --config kind-confing.yaml
Kustomization
今回のように Nginx を立てるだけなら、1 つのファイルに Deployment や Service をまとめても良いと思います。
Group related objects into a single file whenever it makes sense. One file is often easier to manage than several. See the guestbook-all-in-one.yaml file as an example of this syntax.
関連するオブジェクトを1つのファイルにまとめる。複数のファイルよりも1つのファイルの方が管理しやすいことがよくあります。この構文の例として、guestbook-all-in-one.yaml ファイルをご覧ください。
参考:Configuration Best Practices - kubernetes.io
しかし、業務ではファイルをリソースごとに分ける場合が多いので、リソースごとにファイルを分けます。
Resources should be organized by kind, where the resource is in a file that is the lower-case hyphenized form of the Resource kind.
リソースは種類ごとに整理されるべきで、リソースは、リソースの種類を小文字のハイフンで繋げた形のファイルに格納します。
参考:Resource file naming - Kustomize Best Practices.md
$ tree
.
├── base
│ ├── deployment.yaml
│ ├── ingress.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays
└── development
└── kustomization.yaml
参考:2) Create variants using overlays - kubectl.docs.kubernetes.io
単一ファイルの Ingress-Nginx Controller をデプロイする
kind で Ingress リソースを使用できるようにするために、ingress-nginx をデプロイします。
$ k apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
$ k get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-jk67f 0 /1 Completed 0 18 s
ingress-nginx-admission-patch-czs55 0 /1 Completed 1 18 s
ingress-nginx-controller-6b7f45576b-gdrms 0 /1 Running 0 18 s
kustomization.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : default
resources :
- deployment.yaml
- service.yaml
- ingress.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : default
images :
- name : nginx
newTag : stable-alpine3.17-slim
replicas :
- name : my-app
count : 3
resources :
- ../../base
参考:The Kustomization File - kubectl.docs.kubernetes.io
nginx イメージのタグが上書きされていることが確認できますね。
$ kustomize build base | grep image:
- image: nginx:latest
$ kustomize build overlays/development | grep image:
- image: nginx:stable-alpine3.17-slim
参考:images - kubectl.docs.kubernetes.io
Deployment の replicas も上書きできています。
$ kustomize build base | grep replicas:
$ kustomize build overlays/development | grep replicas:
replicas: 3
参考:replicas - kubectl.docs.kubernetes.io
base/(resource).yaml
必要なリソースを作成します。
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : my-app
name : my-app
spec :
selector :
matchLabels :
app : my-app
template :
metadata :
labels :
app : my-app
spec :
containers :
- image : nginx:latest
name : nginx
ports :
- containerPort : 80
resources : {}
restartPolicy : Always
apiVersion : v1
kind : Service
metadata :
name : my-app
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 8080
targetPort : 80
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : my-app
spec :
rules :
- http :
paths :
- pathType : Prefix
path : /
backend :
service :
name : my-app
port :
number : 8080
kustomize でアプリケーションをデプロイする
$ kustomize build overlays/development | k apply -f -
service/my-app created
deployment.apps/my-app created
ingress.networking.k8s.io/my-app created
3つの Pod を作成しました。Pod 名のサフィックスは hdrq6
、tglfn
、xvc5h
です。
$ k get pod
NAME READY STATUS RESTARTS AGE
my-app-74569ccd-hdrq6 1 /1 Running 0 4 m46s
my-app-74569ccd-tglfn 1 /1 Running 0 4 m46s
my-app-74569ccd-xvc5h 1 /1 Running 0 4 m46s
localhost:8080 に curl をし、stern を使ってログを確認します。
$ for x in { 1 ..10} ; do curl localhost:8080 -o /dev/null -w '%{http_code}\n' -s ; sleep 1 ; done
$ stern my-app | awk '{print $1}'
意図した通りにリクエストが振り分けられたので、目標を達成しました。
kustomization 片付け
作成したリソース、ingress-nginx を削除します。
$ kustomize build overlays/development | k delete -f -
$ k delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
Helm
まずは土台となるチャートを作成しておきます。
helm で Ingress-Nginx Controller をデプロイする
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update
Caution
このまま helm install
しても Ingress は使えません。
理由:
Deployment の .spec.template.spec.containers.ports.hostPort
に port が設定されていない
tolerations
が設定されていないため、control-plane に ingress-ngnix-controller がスケジュールされない
Deployment (name: ingress-nginx-controller) に ingress-ready: "true"
の nodeSelector が設定されていない
詳細:
helm install ingress-nginx/ingress-nginx -n ingress-nginx
をそのまま実行すると、ingress-nginx-controller の Pod が worker ノードにスケジュールされてしまいます。
クラスターを作成する 、では以下のように、control-plane にラベルをつけていました。
apiVersion : kind.x-k8s.io/v1alpha4
kind : Cluster
name : sandbox
nodes :
- role : control-plane
image : kindest/node:v1.27.1
kubeadmConfigPatches :
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
...
そして、Kustomization のときはhttps://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
のマニフェストを使って ingress-nginx-controller をデプロイしました。
このマニフェストは Deployment (name: ingress-nginx-controller) に nodeSelector
があり ingress-ready: "true"
を設定していました。さらに tolerations
も設定されていため、Deployment に紐づく Pod が control-plane に配置され、Ingress が使用できました。
$ curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml | yq 'select(.kind=="Deployment") | .spec.template.spec.containers[].ports'
- containerPort: 80
hostPort: 80
name: http
protocol: TCP
- containerPort: 443
hostPort: 443
name: https
protocol: TCP
- containerPort: 8443
name: webhook
protocol: TCP
$ curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml | grep -A 6 tolerations
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Equal
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Equal
$ curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml | grep -C 1 ingress-ready
nodeSelector:
ingress-ready: "true"
kubernetes.io/os: linux
helm template
で確認すると、設定がありません。
$ helm template ingress-nginx ingress-nginx/ingress-nginx | grep ingress-ready
# 何も出力されない
$ helm template ingress-nginx ingress-nginx/ingress-nginx | grep -A 6 tolerations
# 何も出力されない
# hostPorts がない
$ helm template ingress-nginx ingress-nginx/ingress-nginx | yq 'select(.kind=="Deployment") | .spec.template.spec.containers[].ports'
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
values.yaml
を作成し、controller の nodeSelector
や tolerations
の設定をします。
appVersionが 1.8.1
なので、chart がある url は ingress-nginx/charts/ingress-nginx - github.com です。
$ helm show chart ingress-nginx/ingress-nginx | grep appVersion
appVersion: 1.8 .1
以下に提示したURLの箇所を修正すれば良さそうです。
https://github.com/kubernetes/ingress-nginx/blob/652a80042222f881ce56b3b689498e2b6fe4de72/charts/ingress-nginx/values.yaml#L80-L82
https://github.com/kubernetes/ingress-nginx/blob/652a80042222f881ce56b3b689498e2b6fe4de72/charts/ingress-nginx/values.yaml#L194-L198
https://github.com/kubernetes/ingress-nginx/blob/652a80042222f881ce56b3b689498e2b6fe4de72/charts/ingress-nginx/values.yaml#L262-L263
ingress-nginx の values.yaml を作成する
$ helm show values ingress-nginx/ingress-nginx > ingress-nginx/values.yaml
controller :
...
hostPort :
enabled : false
enabled : true
...
tolerations : []
tolerations :
- effect : NoSchedule
key : node-role.kubernetes.io/master
operator : Equal
- effect : NoSchedule
key : node-role.kubernetes.io/control-plane
operator : Equal
...
nodeSelector :
kubernetes.io/os : linux
ingress-ready : "true"
values.yaml
の値が設定されています。
$ helm template ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx/values.yaml | grep -C 1 ingress-ready
nodeSelector:
ingress-ready: "true"
kubernetes.io/os: linux
$ helm template ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx/values.yaml | grep -A 6 tolerations
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Equal
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Equal
$ helm template ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx/values.yaml | yq 'select(.kind=="Deployment") | .spec.template.spec.containers[].ports'
- name: http
containerPort: 80
protocol: TCP
hostPort: 80
- name: https
containerPort: 443
protocol: TCP
hostPort: 443
- name: webhook
containerPort: 8443
protocol: TCP
先ほど作成した values.yaml
を使ってデプロイします。
$ helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace -f ingress-nginx/values.yaml
問題なくデプロイできました。
$ k get pod -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-6cf94958b4-n47q9 1 /1 Running 0 37 s 10.244 .0.16 sandbox-control-plane <none> <none>
参考: https://github.com/kubernetes-sigs/kind/issues/1693#issuecomment-1060872664
templates/(resource).yaml
templates/deployment.yaml apiVersion : apps/v1
kind : Deployment
metadata :
name : {{ include "my-app.fullname" . }}
labels :
{{- include "my-app.labels" . | nindent 4 }}
spec :
replicas : {{ .Values.replicaCount }}
selector :
matchLabels :
{{- include "my-app.selectorLabels" . | nindent 6 }}
template :
metadata :
{{- with .Values.podAnnotations }}
annotations :
{{- toYaml . | nindent 8 }}
{{- end }}
labels :
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec :
serviceAccountName : {{ include "my-app.serviceAccountName" . }}
containers :
- name : {{ .Chart.Name }}
securityContext :
{{- toYaml .Values.securityContext | nindent 12 }}
image : "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy : {{ .Values.image.pullPolicy }}
ports :
- name : http
containerPort : 80
protocol : TCP
livenessProbe :
httpGet :
path : /
port : http
readinessProbe :
httpGet :
path : /
port : http
resources :
{{- toYaml .Values.resources | nindent 12 }}
templates/service.yaml apiVersion : v1
kind : Service
metadata :
name : {{ include "my-app.fullname" . }}
labels :
{{- include "my-app.labels" . | nindent 4 }}
spec :
type : {{ .Values.service.type }}
ports :
- port : {{ .Values.service.port }}
targetPort : http
protocol : TCP
name : http
selector :
{{- include "my-app.selectorLabels" . | nindent 4 }}
templates/ingress.yaml {{- if .Values.ingress.enabled - }}
{{- $fullName := include "my-app.fullname" . - }}
{{- $svcPort := .Values.service.port - }}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className }}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion - }}
apiVersion : networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion - }}
apiVersion : networking.k8s.io/v1beta1
{{- else - }}
apiVersion : extensions/v1beta1
{{- end }}
kind : Ingress
metadata :
name : {{ $fullName }}
labels :
{{- include "my-app.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations :
{{- toYaml . | nindent 4 }}
{{- end }}
spec :
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName : {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls :
{{- range .Values.ingress.tls }}
- hosts :
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName : {{ .secretName }}
{{- end }}
{{- end }}
rules :
{{- range .Values.ingress.hosts }}
- host : {{ .host | quote }}
http :
paths :
{{- range .paths }}
- path : {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType : {{ .pathType }}
{{- end }}
backend :
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service :
name : {{ $fullName }}
port :
number : {{ $svcPort }}
{{- else }}
serviceName : {{ $fullName }}
servicePort : {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
Tips: helm のテンプレートは変数を定義できます。
{{- $fullName := include "my-app.fullname" . - }}
Variables - helm.sh
values.yaml
values.yaml replicaCount : 3
image :
repository : nginx
pullPolicy : IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag : ""
nameOverride : ""
fullnameOverride : ""
serviceAccount :
create : false
annotations : {}
name : ""
podAnnotations : {}
securityContext : {}
service :
type : ClusterIP
port : 8080
ingress :
enabled : true
className : ""
annotations : {}
hosts :
- paths :
- path : /
pathType : Prefix
resources : {}
helm でアプリケーションをデプロイする
$ helm install my-app my-app
3つの Pod を作成しました。Pod 名のサフィックスは fszgb
、mz84j
、swctv
です。
$ k get pod
NAME READY STATUS RESTARTS AGE
my-app-7c5586dd55-fszgb 1 /1 Running 0 5 s
my-app-7c5586dd55-mz84j 1 /1 Running 0 5 s
my-app-7c5586dd55-swctv 1 /1 Running 0 5 s
localhost:8080 に curl をし、stern を使ってログを確認します。
$ for x in { 1 ..10} ; do curl localhost:8080 -o /dev/null -w '%{http_code}\n' -s ; sleep 1 ; done
$ stern my-app -e kube-probe | awk '{print $1}'
意図した通りにリクエストが振り分けられたので、目標を達成しました。
helm 片付け
$ helm uninstall my-app
$ helm uninstall ingress-nginx -n ingress-nginx
クラスター削除
$ kind delete cluster --name sandbox
まとめ
kind + Ingress-Nginx Controller で Ingress が使える環境を Kustomization、helm で用意してみました。これで Horizontal Pod Autoscaler や Ingress の勉強にも役に立ちそうです。
今回使用したコードは blog-code/2023/07/kind-ingress においています。
Kustomize、helm それぞれで curl を実行しましたが、そのタイミングでブラウザから localhost:8080
を開くと以下の画像のようになります。