ローカルのKukbernetesクラスターにIngressを作成し、localhostにcurlする
はじめに
ローカル環境に立ち上げた 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/v1alpha4kind: Clustername: sandboxnodes:- 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-nginxNAME READY STATUS RESTARTS AGEingress-nginx-admission-create-jk67f 0/1 Completed 0 18singress-nginx-admission-patch-czs55 0/1 Completed 1 18singress-nginx-controller-6b7f45576b-gdrms 0/1 Running 0 18s
kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationnamespace: defaultresources: - deployment.yaml - service.yaml - ingress.yaml
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationnamespace: 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/v1kind: Deploymentmetadata: labels: app: my-app name: my-appspec: 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: v1kind: Servicemetadata: name: my-appspec: selector: app: my-app ports: - protocol: TCP port: 8080 targetPort: 80
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-appspec: 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 createddeployment.apps/my-app createdingress.networking.k8s.io/my-app created
3つの Pod を作成しました。Pod 名のサフィックスは hdrq6
、tglfn
、xvc5h
です。
$ k get podNAME READY STATUS RESTARTS AGEmy-app-74569ccd-hdrq6 1/1 Running 0 4m46smy-app-74569ccd-tglfn 1/1 Running 0 4m46smy-app-74569ccd-xvc5h 1/1 Running 0 4m46s
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 create my-app
helm で Ingress-Nginx Controller をデプロイする
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx$ helm repo update
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 appVersionappVersion: 1.8.1
以下に提示したURLの箇所を修正すれば良さそうです。
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 wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESingress-nginx-controller-6cf94958b4-n47q9 1/1 Running 0 37s 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/v1kind: Deploymentmetadata: 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: v1kind: Servicemetadata: 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: Ingressmetadata: 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 }}
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 podNAME READY STATUS RESTARTS AGEmy-app-7c5586dd55-fszgb 1/1 Running 0 5smy-app-7c5586dd55-mz84j 1/1 Running 0 5smy-app-7c5586dd55-swctv 1/1 Running 0 5s
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
を開くと以下の画像のようになります。