Kubernetes入門
ここは何?
ɯ̹t͡ɕʲiがKubernetesに入門してホームページを公開するまでに調べたことを書き留める場所です。
はじめに
Kubernetesとは
Googleが2014年から開発しているコンテナオーケストレーションツール
現在は Cloud Native Computing Foundation がメンテナンスしているとのこと
読みはクバネテス、クーべネティス
most people on the team say: koo-ber-net-ees, or just 'k8s' or k-eights
コンテナ技術の話題でしばしば上がるので、その際はk8sと略される
なんでKubernetes?
普段、開発環境の構築にdocker-composeを利用している
アプリケーションはDockerイメージにしていて、コンテナ間の協調はdocker-composeで行っている
開発まではdocker-composeで十分だが、アプリケーションをデプロイするのが難しい
1. アプリケーションのリソースとイメージ、docker-compose.ymlをターゲットサーバーに配置
2. リソースの適切な配置と認証情報等の設定値を環境変数に書き込み
3. docker-compose up -d
デプロイできたとして、継続的な更新が難しい
1. ターゲットサーバー上で docker pull <iamge> または Dockerfile の再配置(リソースや設定の更新があればそれも再配置)
2. docker-compose stop && docker-compose up -d
上記のような更新方法では、以下の問題がある
デプロイの自動化が難しい。自動化の仕組みを自前で用意する必要がある
コンテナのビルド・再起動の間、ダウンタイムが発生する
docker-composeでサーバー内のスケーリングまではできるが、サーバーを跨いだスケーリングや協調動作はできない
そういうのはdocker swarmの仕事だが、オーケストレーションはKubernetesでやるのがデファクトスタンダードになっている
サーバーを跨いだサービスの構築を遊びのアプリでやる予定はないが、このクラウド全盛時代にあえて対応しない理由もない
他のDockerイメージでサービスを構築するシステムとの比較
dokku
dokkuが走ってるサーバにgit pushするといい感じにビルドしてサービスを立ち上げてくれる
当初はこれで十分かと思っていたが、dokkuコマンドでサーバーの設定をしないといけないので、管理対象が増えるのがいやだなとなった
データの永続化のためにアプリとDBサーバーをリンクするプラグインを導入しないといけないので、DBサーバのコンテナをいじりづらい
サブドメインでアプリが公開されるのも微妙、アプリごとに自由にやりたい
サーバをdokku専用マシンにするつもりはない、アプリが指定のポートで立ち上がってほしいだけ、という用途に向かない
利便性のために自由度が低い、ならKubernetesで管理のほうがいいかとなった
Kubernetesの導入
現在ではDocker Desktopに標準で組み込まれているので、Dockerの設定からKubernetesを有効にするだけで使い始めることができる
初期設定
1. Dockerの設定を開く
2. Kubernetesのタブ
3. "Enable Kubernetes"にチェックを入れて"Apply"
ついでにdocker stackのデフォルトオーケストレーターをKubernetesにした
起動しない場合の対処1
1. Dockerの設定を開く
2. Advancedのタブ
3. MemoryとSwapを上げる(4GBと4GBにした)
起動しない場合の対処2
1. ~/.kubeを削除
2. Dockerの設定を開く
3. Resetのタブ
4. Reset Kubernetes
起動しない場合の対処3
1. Dockerの設定を開く
2. Resetのタブ
3. Reset to factory defaults
DockerのVMごと消えるのでマシンで作成したイメージ、コンテナがすべて削除されるため注意
ここまでやって起動しない場合
どうしたらいいんでしょうか
https://gyazo.com/953d99f7c77a2d970732fd943d754c93
macOSでは 3. で上手くいったけど、Windowsではダメだった…
直ったので手順を転載しておきます。
やったこと
・Docker Desktopをアンインストール
・C:\Program Files\Docker\Dockerを削除
・C:\ProgramData\DockerDesktopを削除
・C:\Users\Private\AppData\Local\Dockerを削除
・C:\Users\Private\AppData\Roaming\Dockerを削除
・Docker Desktopを再インストール
・DockerのDNS設定を8.8.8.8に
動作確認
code:console
$ kubectl cluster-info
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* docker-desktop docker-desktop docker-desktop
docker-for-desktop docker-desktop docker-desktop
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
docker-desktop Ready master 18m v1.14.8
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20m
$ kubectl get deployment
No resources found.
$ kubectl get pods
No resources found.
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
docker compose-6c67d745f6-8xqqc 1/1 Running 0 30m
docker compose-api-57ff65b8c7-sgwfv 1/1 Running 0 30m
kube-system coredns-6dcc67dcbc-755rc 1/1 Running 0 31m
kube-system coredns-6dcc67dcbc-blnf9 1/1 Running 0 31m
kube-system etcd-docker-desktop 1/1 Running 0 30m
kube-system kube-apiserver-docker-desktop 1/1 Running 0 30m
kube-system kube-controller-manager-docker-desktop 1/1 Running 0 30m
kube-system kube-proxy-qj2m8 1/1 Running 0 31m
kube-system kube-scheduler-docker-desktop 1/1 Running 0 30m
Kubernetesのリソース
このあたりを読むと良い。
Kubernetesにおけるオブジェクト
Kubernetesでは、リソースをいくつかのかたまりで管理する。このかたまり(ビルディングブロック)をオブジェクトとして、オブジェクト単位で操作することでリソースの管理を容易にしている。
ポッド(Pod)
Kubernetesにおけるリソースの最小単位。1つ以上のコンテナからなる。KubernetesのDeploymentはPodの単位で各ノードへデプロイを実施し、サービスを構成する。
サービス(Service)
ポッドは各ノードへ散らばってデプロイされるため、それらに外部からアクセスするためには各ノードのアドレスを知る必要がある。サービスはノードをまとめ上げ、単一のエンドポイントを提供することで、ポッドやノードを抽象化する。
ボリューム(Volume)
Kubernetesコンテナでは、デフォルトではファイルシステムに一時ストレージを使用する。そのため、コンテナを再起動すると情報は失われる。情報を永続化するには、永続ボリューム(PersistentVolume)の定義と、Podが永続ボリュームを要求する際の請求情報(PersistentVolumeClaim)を作成し、Podの定義に請求情報の名前を追加する。
ネームスペース(Namespace)
同一の物理クラスター上で複数の仮想クラスターを定義するために使用する。ネームスペースによってクラスターは切り分けられ、本番環境、ステージング環境、開発環境などを安全に分離することができる。
リソースは必ずしもネームスペースに属するとは限らない。例えば、ノードや永続ボリュームといった低レベルのリソースは、ネームスペースを跨いで利用される。これは、ひとつのノードに異なるネームスペースのポッドが入りうることを意味する。
リソースの作成と削除
リソースを作って動かしてみる。
ポッドの作成
code:console
$ kubectl create deployment testnginx --image nginx
deployment.apps/testnginx created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
testnginx 1/1 1 1 80s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
testnginx-756c979478-jcv8m 1/1 Running 0 99s
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
testnginx-756c979478 1 1 1 8m46s
testnginxという名前でデプロイメントを作成し、それに管理されるポッドが出来た。また、ポッドを安定稼働させるための監督者であるレプリカセット(ReplicaSet)も同時に作成されている。
それぞれ、Deployment > ReplicaSet > Podの親子関係にある。デプロイメントが稼働させたいポッドのレプリカ数を指定し、レプリカセットがそれに従いポッドを追加、削除してレプリカ数を維持することで、安定した稼働を実現する。
また、デプロイメントもレプリカセットを管理している。デプロイメントはデプロイ時に新しいレプリカセットを作成し、旧レプリカセットが管理している旧ポッドとの数を調整しながら新ポッドを増やしていき、最終的に新レプリカセットで置き換える。レプリカセットのバージョン管理もしているらしい。
サービスの作成
code:console
$ kubectl expose deployment testnginx --port 80 --type NodePort
service/testnginx exposed
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 116m
testnginx NodePort 10.101.185.80 <none> 80:32386/TCP 55s
ポート32386でtestnginxにアクセスできるサービスが作成された。--target-portでポートの指定も可能。
アプリケーションのデプロイ
code:console
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
この状態で にアクセスするとダッシュボードが開いた。 下記を参考にアクセストークンを取得してログインしてみる。
code:console
$ kubectl -n kubernetes-dashboard get secret
NAME TYPE DATA AGE
default-token-mks78 kubernetes.io/service-account-token 3 5m35s
kubernetes-dashboard-certs Opaque 0 5m35s
kubernetes-dashboard-csrf Opaque 1 5m35s
kubernetes-dashboard-key-holder Opaque 2 5m35s
kubernetes-dashboard-token-2wccw kubernetes.io/service-account-token 3 5m35s
$ kubectl -n kubernetes-dashboard describe secrets kubernetes-dashboard-token-2wccw
Name: kubernetes-dashboard-token-2wccw
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: kubernetes-dashboard
kubernetes.io/service-account.uid: ef960234-3acd-11ea-85d4-025000000001
Type: kubernetes.io/service-account-token
Data
====
***
取得できたトークンでログイン成功。
kubernetesのymlでもdocker-compose.ymlでもできるとのことなので、最終的にはu.chimata.orgのdocker-compose.ymlでもやってみたい。
ポッドの削除
code:console
$ kubectl delete deployment testnginx
deployment.extensions "testnginx" deleted
$ kubectl get deployment
No resources found.
$ kubectl get pods
No resources found.
$ kubectl get rc
No resources found.
デプロイメントを削除すると子要素もすべて消える。ダッシュボードを見ながらやると、デプロイメントが消えた後、ポッドが削除されていく様子を確認できる。
サービスの削除
code:console
$ kubectl delete service testnginx
service "testnginx" deleted
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h40m
サービスはデプロイメントの要素ではないので別に削除する。
アプリケーションの削除
code:console
namespace "kubernetes-dashboard" deleted
serviceaccount "kubernetes-dashboard" deleted
service "kubernetes-dashboard" deleted
secret "kubernetes-dashboard-certs" deleted
secret "kubernetes-dashboard-csrf" deleted
secret "kubernetes-dashboard-key-holder" deleted
configmap "kubernetes-dashboard-settings" deleted
role.rbac.authorization.k8s.io "kubernetes-dashboard" deleted
clusterrole.rbac.authorization.k8s.io "kubernetes-dashboard" deleted
rolebinding.rbac.authorization.k8s.io "kubernetes-dashboard" deleted
clusterrolebinding.rbac.authorization.k8s.io "kubernetes-dashboard" deleted
deployment.apps "kubernetes-dashboard" deleted
service "dashboard-metrics-scraper" deleted
deployment.apps "dashboard-metrics-scraper" deleted
アプリケーションのデプロイに使用したKubernetesの設定ファイルを使えば、定義してあるリソースを削除することができる。
kustomization.yamlによるリソースの管理
リソースの作成と削除で行った一連の操作を kustomization.yamlで行ってみる。
kustomization.yamlって?
kustomizeというkubernetesの定義ファイルをパッケージングするツールがあり、kubectl v1.14からは標準でkustomization.yamlを利用することができる。 kustomization.yaml自体は、各リソースの定義ファイルのパスやメタデータを記述したものになっている。その書式は以下。
kustomization.yamlの作成
testnginxの定義ファイル
code:testnginx-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: testnginx
labels:
app: testnginx
spec:
replicas: 1
selector:
matchLabels:
app: testnginx
template:
metadata:
labels:
app: testnginx
spec:
containers:
- name: testnginx
image: nginx
ports:
- containerPort: 80
code:testnginx-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: testnginx
spec:
selector:
app: testnginx
ports:
- protocol: TCP
port: 80
targetPort: 32386
type: NodePort
一旦上記で動作することを確認する。
code:console
$ kubectl appply -f testnginx-deployment.yaml,testnginx-service.yaml
deployment.apps/testnginx created
service/testnginx created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
testnginx 1/1 1 1 63s
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
testnginx-5f4989897d 1 1 1 102s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
testnginx-5f4989897d-fh977 1/1 Running 0 2m16s
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h
testnginx NodePort 10.99.16.214 <none> 80:32006/TCP 3m7s
公開ポートが違う…
code:testnginx-service-fix.yaml
---
apiVersion: v1
kind: Service
metadata:
name: testnginx
spec:
selector:
app: testnginx
ports:
- protocol: TCP
port: 80
nodePort: 32386
type: NodePort
再チャレンジ
code:console
$ kubectl apply -f testnginx-deployment.yaml,testnginx-service-fix.yaml
deployment.apps/testnginx unchanged
service/testnginx configured
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h
testnginx NodePort 10.99.16.214 <none> 80:32386/TCP 10m
今度は期待するポート番号が設定された。
一旦リソースを削除しておく。
code:console
$ kubectl delete -f testnginx-deployment.yaml,testnginx-service-fix.yaml
deployment.apps "testnginx" deleted
service "testnginx" deleted
kustomization.yamlで設定
今度はkustomization.yamlで設定してみる。
code:kustomization.yaml
---
resources:
- testnginx-deployment.yaml
- testnginx-service-fix.yaml
-k オプションでkustomization.yamlを読み込む。
code:console
$ kubectl apply -k .
service/testnginx created
deployment.apps/testnginx created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
testnginx 1/1 1 1 27s
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
testnginx-5f4989897d 1 1 1 54s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
testnginx-5f4989897d-ww4dd 1/1 Running 0 75s
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h
testnginx NodePort 10.104.188.110 <none> 80:32386/TCP 101s
リソースの作成ができた。
code:console
$ kubectl delete -k .
service "testnginx" deleted
deployment.apps "testnginx" deleted
$ kubectl get deployment
No resources found.
$ kubectl get rs
No resources found.
$ kubectl get pods
No resources found.
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h
削除もできた。
イングレスによるサービスの外部ネットワークへの公開
Ingress
サービスをNodePortやLoadBalancerで作成することで、サービスを直接公開することは可能だが、それぞれ以下の問題がある。
NodePort: ひとつのサービスにつきひとつのポートしか公開できない
LoadBalancer: ひとつのサービスにひとつのIPが振られることになり、クラウドサービスでの利用は費用面で不利
どちらも: 払い出すポート、IPが被らないように個別に管理する必要が出てくる
そこで、
internet
|
--|-----|--
上記のような構成にすることで、サービスをより柔軟にインターネットに公開することができる。
NGINX Ingress Controllerの導入
イングレスを実際に機能させるためにはリソースの作成の前にIngress Controllerを用意する必要がある。
code:console
$ kubectl apply -f ingress-nginx-mandatory.yaml,ingress-nginx-service-cloud-generic.yaml
namespace/ingress-nginx created
serviceaccount/nginx-ingress-serviceaccount created
role.rbac.authorization.k8s.io/nginx-ingress-role created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
service/ingress-nginx created
kustomization.yamlで適用もできる。
code:kustomization.yaml
---
resources:
- ingress-nginx-mandatory.yaml
- ingress-nginx-service-cloud-generic.yaml
- testnginx-deployment.yaml
- testnginx-service-fix.yaml
code:console
$ kubectl apply -k .
Ingress Controllerを導入したので、実際にイングレスを作成してサービスを公開してみる。
code:ingress.yaml
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: testnginx
servicePort: 80
code:kustomization.yaml
---
resources:
- ingress-nginx-mandatory.yaml
- ingress-nginx-service-cloud-generic.yaml
- testnginx-deployment.yaml
- testnginx-service-fix.yaml
- ingress.yaml
code:console
$ kubectl apply -k .
namespace/ingress-nginx unchanged
serviceaccount/nginx-ingress-serviceaccount unchanged
role.rbac.authorization.k8s.io/nginx-ingress-role unchanged
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole unchanged
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding unchanged
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding unchanged
configmap/nginx-configuration unchanged
configmap/tcp-services unchanged
configmap/udp-services unchanged
service/testnginx unchanged
deployment.apps/nginx-ingress-controller unchanged
deployment.apps/testnginx unchanged
ingress.networking.k8s.io/ingress-nginx created
limitrange/ingress-nginx configured
動いた。
kustomization of a helm chart
Helm
HelmはChartという単位でアプリケーションをデプロイできるKubernetesのパッケージマネージャ。パッケージはHelm Hubで探すことができる。 kustomizeでhelm chartをデプロイする
Helmはhelmコマンドでアプリケーションをデプロイするが、kustomization.yamlですべてを管理したい。
下記を参考に挑戦する。
grafanaをデプロイするkustomization.yamlを作成する。
code:kustomization.yaml
---
generators:
- chartInflator.yaml
code:chartInflator.yaml
---
apiVersion: someteam.example.com/v1
kind: ChartInflator
metadata:
name: notImportantHere
chartName: grafana
code:console
$ plugin=plugin/someteam.example.com/v1/chartinflator/ChartInflator
curl -s --create-dirs -o \
"./kustomize/$plugin" \
kubernetes-sigs/kustomize/master/$plugin"
$ chmod a+x ./kustomize/$plugin
ところで、Helmは今後stable/xにChartを置くのをやめて、それらの利用を非推奨にするような雰囲気を感じる(要出典)ので、オリジナルのChartInflatorを作ったほうがいいような気がする。
動かしてみる。
code:console
$ kubectl apply -k .
error: json: unknown field "generators"
$ kubectl kustomize .
Error: json: unknown field "generators"
はいおしまい。kubectlはkustomizeプラグイン対応してない。
素直にhelmでデプロイするか、kustomize用意してプラグイン作ってがんばるか悩むところ。
下記記事を読んだりいろいろ考えた結果、Helmはアプリケーション配布の場面以外では使わなくていいかなとなった。やはり各アプリケーションごとの設定ファイル学習コストが高い。
ということで、最新のkustomizeを用意して kustomize build . | kubectl apply -f - でまわそう
kubectl-krew について
Prometheus と Grafana の導入
Helmで入れたかったモニタリングアプリケーションの Prometheus と Grafana だが、親切なことに NGINX Ingress Controller のチュートリアルで定義ファイルを用意してくれている。
Prometheus
Grafana
Grafanaは、UIがかっこいいログ・データ可視化アプリケーション。Prometheusからのログ取得に対応している。 2021-05-04現在、ingress-nginx名前空間に ingress-nginxコントローラーがあることを前提としているので、前述の手順でデプロイしたcontrollerは削除する。
code:console
$ kubectl delete -k .
あらためて、ingress-nginx コントローラーとPrometheus, Grafanaをデプロイする。
code:console
$ kubectl apply -f ingress-nginx-controller.yaml && \
kubectl apply -k github.com/kubernetes/ingress-nginx/deploy/prometheus/ && \
kubectl apply -k github.com/kubernetes/ingress-nginx/deploy/grafana/
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
serviceaccount/prometheus-server created
role.rbac.authorization.k8s.io/prometheus-server created
rolebinding.rbac.authorization.k8s.io/prometheus-server created
configmap/prometheus-configuration-bc6bcg7b65 created
service/prometheus-server created
deployment.apps/prometheus-server created
service/grafana created
deployment.apps/grafana created
Web UIを確認してみる。
code:console
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.108.135.94 <none> 3000:32397/TCP 89s
ingress-nginx-controller LoadBalancer 10.111.10.237 <pending> 80:32589/TCP,443:32325/TCP 113s
ingress-nginx-controller-admission ClusterIP 10.97.212.255 <none> 443/TCP 113s
prometheus-server NodePort 10.98.83.48 <none> 9090:30074/TCP 98s
localhost:30074, localhost:32397で無事アクセスできた。ただ、監視がうまくいかない……
Nginx Ingress Controlerのバージョンについて
Nginx Ingress Controllerには、k8sコミュニティ版とNGINX社版があり、サポートする機能に違いがある。
それぞれの違いについては次のドキュメントを参照。
検索して出てくる-enable-prometheus-metricsオプションやprometheus.ioアノテーションはNGINX社版の設定のため注意。
永続ボリュームの設定
練習に、nginxで自分で作成したhtmlを配信してみる。
まずは nginxをサービスする。
code:nginx.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pv-claim
labels:
app: nginx
spec:
storageClassName: local-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 0.5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
volumeMounts:
- name: nginx-persistent-storage
mountPath: /usr/share/nginx/html/
volumes:
- name: nginx-persistent-storage
persistentVolumeClaim:
claimName: nginx-pv-claim
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
PersistentVolumeClaimで指定したstorageClassNameに永続ボリュームをリクエストする。
このPersistentVolumeClaimにボリュームを提供する永続ボリュームと、ローカル環境で使用するストレージクラスを定義する。
code:persistent-volume.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-org-chimata
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /Users/private/Projects/github.com/c18t/chimata.org/data
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- docker-desktop
/Users/private/Projects/github.com/c18t/chimata.org/data/test.htmlにファイルを置いて、サービスにアクセスしてみる。
code:console
$ kubectl apply -f persistent-volume.yaml,nginx.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-b94b554f9-b7ngn 1/1 Running 0 59m
$ kubectl port-forward nginx-deployment-b94b554f9-b7ngn 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from ::1:8080 -> 80 $ curl -L localhost:8080/test.html
<p>テスト</p>
ファイルの配置で配信ができた。
Ingressの更新
上記のnginxをIngressで公開する。
code:ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-nginx
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: 'nginx'
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
apiVersionが上がった。
デプロイしたNginx Ingress Controllerを使うには、kubernetes.io/ingress.class: 'nginx'が必要なようだ。
調べたところ、Ingressのspec設定で公開するポートを別のものに変える方法はなさそう。個別にportをexposeする方法は下記を参照。
macOSで最初から起動しているapacheが80番を取ってしまっているのでサービスを止める。
code:console
$ sudo apachectl stop
Ingressのデプロイ。
code:console
$ kubectl apply -f ingress.yaml
うまくいった。
Google Kubernetes Engine (GKE)へデプロイ
クラウドサービスにデプロイしてみる。金額的な面からGKEを採用する。
Kubernetes構成の操作を行える(=変更や適用をgitで行える)kptというのがあるようだけどそれはまた今度で。アプリケーションと構成のリポジトリ分けるのは確かになと思う。
異なる環境へのデプロイを考慮したKubernetes構成
開発環境やステージング環境、本番環境など対象の環境ごとに設定を変更することが考えられる。
環境間で共通の構成をbaseフォルダ、環境毎のパッチ構成をoverlays/(env)フォルダに配置する規約でファイルを作成すると、kubectl kustomize ./overlays/(env)/でbaseに(env)環境のパッチがあたった状態のKubernetes構成ファイルを生成することができる。
code:console
$ tree ./k8s/
./k8s/
├── ingress-nginx-controller.yaml
├── base
│ ├── ingress.yaml
│ ├── kustomization.yaml
│ └── nginx.yaml
└── overlays
├── development
│ ├── kustomization.yaml
│ ├── nameprefix.yaml
│ ├── nginx-patch.yaml
│ └── persistent-volume.yaml
└── production
├── kustomization.yaml
└── nameprefix.yaml
4 directories, 10 files
上記のような構成になった。
baseフォルダに今まで作成したファイルを配置し、overlays/development, productionフォルダを作成してパッチを記述した。
code:base/kustomization.yaml
---
resources:
- ingress.yaml
- nginx.yaml
ingress-nginx-controller.yamlはoverlays/(env)でnamePrefixを指定したときにmetadata/nameが変わって動かなくなるため、base,overlaysフォルダから取り出し、個別にkubectl apply -fするようにした。
code:base/nginx.yaml.diff
***************
*** 6,12 ****
labels:
app: nginx
spec:
- storageClassName: local-storage
accessModes:
- ReadWriteOnce
resources:
--- 6,11 ----
PersistentVolumeClaimからstorageClassNameを削除し、環境毎に設定するようにした。storageClassNameを指定しない場合はクラスターのデフォルトのStorageClassが使用される。kubectl get storageclassで確認可能。
code:overlays/development/kustomization.yaml
---
namePrefix: dev-
bases:
- ../../base
resources:
- persistent-volume.yaml
configurations:
- nameprefix.yaml
patchesStrategicMerge:
- nginx-patch.yaml
basesにベースとなる構成フォルダを指定する。
resourcesに環境特有のリソースを指定する。ここにpersistent-volume.yamlを持ってきた。
patchesStrategicMergeにベースのリソースへのパッチを指定する。
namePrefixに指定した値がデプロイされるすべてのリソースに接頭辞として付与される。
kubectl v1.19のkustomizeはv2.0.3のため、ドキュメントを検索する際は注意。
code:overlays/development/nameprefix.yaml
---
namePrefix:
- kind: Ingress
path: spec/defaultBackend/service/name
- kind: Ingress
path: spec/rules/http/paths/backend/service/name
- kind: PersistentVolume
path: spec/storageClassName
- kind: PersistentVolumeClaim
path: spec/storageClassName
kustomization.yamlのnamePrefixはすべてのフィールドを期待通り変えてくれるわけではないようで、一部のフィールドの指定がリソース名とずれてしまい動かなかった。そのため、configurationsでtransformの動作を変更し、正しい名前になるように修正した。
code:overlays/development/nginx-patch.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pv-claim
spec:
storageClassName: local-storage
apiVersion, kind, metadata/namespace,name,uidが一致したリソースを変更する。
パッチのルールは下記のあたりを参照。
設定をファイルから読み込む
persistent-volume.yaml の spec/local/path を設定ファイルから読み込んで、設定ファイル自体はコミットしないようにしたい。
configMapGeneratorとvarsを組み合わせれば実現可能。
code:overlays/development/kustomization.yaml.diff
***************
*** 7,10 ****
--- 7,22 ----
configurations:
- nameprefix.yaml
+ - var-reference.yaml
patchesStrategicMerge:
- nginx-patch.yaml
+ configMapGenerator:
+ - name: template-vars
+ env: template-vars.env
+ vars:
+ - name: PV_LOCAL_PATH
+ objref:
+ apiVersion: v1
+ kind: ConfigMap
+ name: template-vars
+ fieldref:
+ fieldpath: data.PV_LOCAL_PATH
code:overlays/development/template-vars.env
PV_LOCAL_PATH=/Users/private/Projects/github.com/c18t/chimata.org/data
configMapGeneratorで下記のconfigMapが生成される。
code:template-vars.yaml
apiVersion: v1
data:
PV_LOCAL_PATH: /Users/private/Projects/github.com/c18t/chimata.org/data
kind: ConfigMap
metadata:
name: dev-template-vars-g8f84845gt
varsの指定によって、template-vars の data/PV_LOCAL_PATH の値が変数PV_LOCAL_PATHとして定義される。
code:overlays/development/persistent-volume.yaml.diff
***************
*** 22,24 ****
local:
! path: $(PV_LOCAL_PATH)
nodeAffinity:
--- 22,24 ----
local:
! path: /Users/private/Projects/github.com/c18t/chimata.org/data
nodeAffinity:
code:overlays/development/var-reference.yaml
---
varReference:
- kind: PersistentVolume
path: spec/local/path
あとは変数をファイルの利用したい箇所に記述し、変数を参照しているフィールドのパスをconfigurationsのvarReferenceで定義すれば、Kubernetes構成ファイルのビルド時に変数を埋め込んでくれる。
gcloudの準備
code:console
$ gcloud init
無料枠を確認して、非プリエンプティブルなf1-microが1つ無料で使えるus-west1,us-west1-cをデフォルトのリージョン、ゾーンに設定した。 code:console
$ gcloud config configurations list
で、gcloud sdkで登録した設定を確認できる。
GKEの準備
GKEが有効になったらクラスターを作成する。クラスターの名前はorg-chimataにした。
クラスターが完成したらローカルから操作できるように認証情報を取得する。
code:console
$ gcloud container clusters get-credentials --zone=us-west1-c org-chimata
$ kubectl config current-context
gke_org-chimata_us-west1-c_org-chimata
接続できたので、GKEクラスターを作成する。
code:console
$ gcloud container clusters create org-chimata --preemptible --machine-type=f1-micro --disk-size=10
:
ERROR: (gcloud.container.clusters.create) ResponseError: code=400, message=Node pools of f1-micro machines are not supported due to insufficient memory.
できない。
注:f1-micro マシンは、GKE を実行するための十分なメモリを搭載していないためサポートされていません。
バージョンアップとともにk8sの要求するメモリが増えてサポートがされなくなった様子。残念。
GKEクラスターの作成と構成のデプロイ
Web UIからクラスターを作成して、いったんノードプールを削除。(ノードプールはGKEの概念)
e2-microのノードが1つのノードプールを作成してから、Kubernetes構成をデプロイする。
code:console
$ gcloud container node-pools create pool-persistent --cluster org-chimata --machine-type=e2-micro --num-nodes=1 --disk-size=10
$ kubectl apply -f ingress-nginx-controller.yaml
$ kubectl apply -k ./overlays/production/
kube-systemのPodだけでメモリの94%を使用してしまっている。メモリが足りずにNginx Ingress ControllerのPodがデプロイできない。
code:console
$ kubectl describe nodes
:
Non-terminated Pods: (9 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system event-exporter-gke-67986489c8-wz8l6 0 (0%) 0 (0%) 0 (0%) 0 (0%) 51m
kube-system fluentbit-gke-cqp6d 100m (10%) 0 (0%) 200Mi (31%) 500Mi (79%) 38m
kube-system gke-metrics-agent-89hz7 3m (0%) 0 (0%) 50Mi (7%) 50Mi (7%) 38m
kube-system kube-dns-5d54b45645-x4m6m 260m (27%) 0 (0%) 110Mi (17%) 210Mi (33%) 51m
kube-system kube-dns-autoscaler-58cbd4f75c-c87p7 20m (2%) 0 (0%) 10Mi (1%) 0 (0%) 51m
kube-system kube-proxy-gke-org-chimata-pool-persistent-fe56b623-t18g 100m (10%) 0 (0%) 0 (0%) 0 (0%) 38m
kube-system l7-default-backend-66579f5d7-dn5v9 10m (1%) 10m (1%) 20Mi (3%) 20Mi (3%) 51m
kube-system pdcsi-node-cn55h 0 (0%) 0 (0%) 0 (0%) 0 (0%) 38m
kube-system stackdriver-metadata-agent-cluster-level-f56bfb654-lzkpp 98m (10%) 48m (5%) 202Mi (32%) 202Mi (32%) 51m
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 591m (62%) 58m (6%)
memory 592Mi (94%) 982Mi (156%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
attachable-volumes-gce-pd 0 0
preemptibleなe2-microノードをもう一つ建ててデプロイできるか試してみる。
code:console
$ gcloud container node-pools create pool-preemptible --preemptible --cluster org-chimata --machine-type=e2-micro --num-nodes=1 --disk-size=10
$ kubectl describe nodes
:
Non-terminated Pods: (7 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
ingress-nginx ingress-nginx-controller-57cb5bf694-tplml 100m (10%) 0 (0%) 90Mi (14%) 0 (0%) 34m
:
デプロイできた。
nginx-serviceにアクセスしてみる。
code:console
$ kubectl get -k ./overlays/production/
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/prod-ingress-nginx <none> * xx.xx.xx.xx 80 70m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
prod-nginx-deployment-568dbfb6d-7nd7r 1/1 Running 0 76m
$ curl -L xx.xx.xx.xx
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.20.0</center>
</body>
</html>
ファイルがないのでフォルダ一覧アクセスで403 Forbidden
index.htmlを配置してみる。
code:console
$ kubectl cp ../data/index.html prod-nginx-deployment-568dbfb6d-7nd7r:/user/share/nginx/html/
$ curl -L xx.xx.xx.xx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
Commercial support is available at
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
表示された。
Cloud Load Balancingを使わないリバースプロキシの構成
Nginx Ingress ControllerではcontrollerのサービスがLoadBalancerで作成される。GKEではLoadBalancerタイプのサービスが作成されると、GCPネットワークサービスのCloud Load Balancingにロードバランサーが作成され、これひとつで月25ドルほどかかる(静的IP:$7.2, 転送ルール:$18.0)。
この金額を節約するため、controllerの定義を修正し、NodePortタイプでサービスが起動するようにする。
現在有効になっているCloud Load Balancing
code:console
$ gcloud compute forwarding-rules list
NAME REGION IP_ADDRESS IP_PROTOCOL TARGET
a6212acb4732446af90049a6d8298808 us-west1 xx.xx.xx.xx TCP us-west1/targetPools/a6212acb4732446af90049a6d8298808
ingress-nginx-controller.yaml を変更する。
code:ingress-nginx-controller.yaml.diff
***************
*** 275,277 ****
spec:
! type: LoadBalancer
externalTrafficPolicy: Local
--- 275,277 ----
spec:
! type: NodePort
externalTrafficPolicy: Local
***************
*** 282,283 ****
--- 282,284 ----
targetPort: http
+ nodePort: 30080
- name: https
***************
*** 286,287 ****
--- 287,289 ----
targetPort: https
+ nodePort: 30443
selector:
サービスで公開するnodePortはkube-apiserverのオプション--service-node-port-rangeで指定された範囲でしか設定できない。GKEではおそらくいじれないので80,443を公開できない。残念。
デプロイしてみる。
code:console
$ kubectl apply -f ingress-nginx-controller.yaml
$ gcloud compute forwarding-rules list
Listed 0 items.
Cloud Load Balancingの設定が消えた。
Cloud Load Balancingでよしなにやってくれていた部分を手動で設定してあげないといけない。
controllerのサービスを外部ネットワークに晒す必要がある。
まずはIPの確認
code:console
$ kubectl get nodes --output wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
gke-org-chimata-pool-persistent-fe56b623-t18g Ready <none> 12h v1.19.9-gke.1400 xx.xx.xx.xx xx.xx.xx.xx Container-Optimized OS from Google 5.4.89+ containerd://1.4.3
gke-org-chimata-pool-preemptible-95243fba-cqlc Ready <none> 11h v1.19.9-gke.1400 yy.yy.yy.yy yy.yy.yy.yy Container-Optimized OS from Google 5.4.89+ containerd://1.4.3
$ kubectl describe nodes
:
ProviderID: gce://org-chimata/us-west1-c/gke-org-chimata-pool-preemptible-95243fba-cqlc
Non-terminated Pods: (8 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
default prod-nginx-deployment-568dbfb6d-chbnq 0 (0%) 0 (0%) 0 (0%) 0 (0%) 20m
ingress-nginx ingress-nginx-controller-57cb5bf694-kx6md 100m (10%) 0 (0%) 90Mi (14%) 0 (0%) 25m
:
ingress-nginx-controller-57cb5bf694-kx6mdはgke-org-chimata-pool-preemptible-95243fba-cqlcのノードに配置されているので、IPはyy.yy.yy.yyとなる。
クラスタのノードへの30080,30443番ポートアクセスを許可するようファイアーウォールを設定する。
code:console
$ gcloud compute firewall-rules create org-chimata-http --allow tcp:30080,tcp:30443
Creating firewall...Created . Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED
org-chimata-http default INGRESS 1000 tcp:30080,tcp:30443 False
code:console
$ curl -L yy.yy.yy.yy:30080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
Commercial support is available at
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
通った。
デプロイ先ノードの固定
ingress-nginx-controllerがプリエンティブルなノードプールで動作しているため、24時間でノードが終了してIPが変わってしまう。
下記を参考に、ingress-nginx-controllerとingressをgke-org-chimata-pool-persistentノードプールに配置されるように設定する。
type: Deploymentの定義を下記のように変更する。
code:ingress-nginx-controler.yaml.diff
***************
*** 398,401 ****
--- 398,402 ----
nodeSelector:
kubernetes.io/os: linux
+ cloud.google.com/gke-nodepool: pool-persistent
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
https://gyazo.com/9452becd5b18aa0ec4d5b3cc3ee2182d
メモリが足りないと怒られる。
kube-system名前空間に配置されたPodsの要求メモリを下げてなんとかならないか。
kubectl describe nodesで調べると、fluentbit-gkeとmetrics-server、stackdriver-metadata-agent-cluster-levelがメモリを多く消費している。それぞれリソースのモニタリングに必要なサービスだが、もうちょっとメモリの上限下げてもいい気がするので下げる。
code:console
$ kubectl edit daemonset/fluentbit-gke --namespace kube-system
上記でうまく編集できなかったのでGCPコンソールから編集する。
code:daemonset/fluentbit-gke.diff
***************
*** 272,279 ****
resources:
limits:
! memory: 250Mi
requests:
cpu: 50m
! memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
--- 272,279 ----
resources:
limits:
! memory: 100Mi
requests:
cpu: 50m
! memory: 50Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
***************
*** 318,325 ****
resources:
limits:
! memory: 250Mi
requests:
cpu: 50m
! memory: 100Mi
securityContext:
allowPrivilegeEscalation: false
--- 318,325 ----
resources:
limits:
! memory: 100Mi
requests:
cpu: 50m
! memory: 50Mi
securityContext:
allowPrivilegeEscalation: false
だめだった。定義を更新してもPodやDaemonSetを削除しても初期値で復活する。
https://gyazo.com/3afcfbfe977c6304cfad613211d725c8
何をやっても初期設定に戻る様子。
いろいろ調べた結果、GKE用Cloud Operationsを切れば、ロギングとモニタリングのためのコンテナが消えるようだ。
Cloud Operationsを切れば、当然ログやイベントのモニタリング、CPUやメモリの使用率グラフの表示はできなくなる。
Cloud Operationsについては下記参照。
GKEクラスタの詳細から、「GKE 用 Cloud Operations」の編集を開き、下記の通りチェックを外して「変更を保存」。
https://gyazo.com/5a2eec4c378b2f14b1f29b62552455f8
code:console
$ kubectl describe nodes
:
ProviderID: gce://org-chimata/us-west1-c/gke-org-chimata-pool-persistent-fe56b623-t18g
Non-terminated Pods: (8 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
default prod-nginx-deployment-568dbfb6d-vvcfl 0 (0%) 0 (0%) 0 (0%) 0 (0%) 11m
ingress-nginx ingress-nginx-controller-7b54fb6f96-wsmwn 100m (10%) 0 (0%) 90Mi (14%) 0 (0%) 12m
kube-system kube-dns-5bcc549f75-zjpsb 260m (27%) 0 (0%) 110Mi (17%) 210Mi (33%) 14m
kube-system kube-dns-autoscaler-58cbd4f75c-c87p7 20m (2%) 0 (0%) 10Mi (1%) 0 (0%) 18h
kube-system kube-proxy-gke-org-chimata-pool-persistent-fe56b623-t18g 100m (10%) 0 (0%) 0 (0%) 0 (0%) 18h
kube-system l7-default-backend-66579f5d7-dn5v9 10m (1%) 10m (1%) 20Mi (3%) 20Mi (3%) 18h
kube-system metrics-server-v0.3.6-6c47ffd7d7-j9knr 48m (5%) 143m (15%) 105Mi (16%) 355Mi (56%) 11m
kube-system pdcsi-node-cn55h 0 (0%) 0 (0%) 0 (0%) 0 (0%) 18h
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 538m (57%) 153m (16%)
memory 335Mi (53%) 585Mi (93%)
:
だいぶスッキリしたしingress-nginx-controllerも無事起動した。
preemptibleのノードプールも必要なくなったので削除する。
code:console
$ gcloud container node-pools delete pool-preemptible --cluster org-chimata
The following node pool will be deleted.
Do you want to continue (Y/n)? y
Deleting node pool pool-preemptible...done.
$ kubectl get nodes --output wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
gke-org-chimata-pool-persistent-fe56b623-t18g Ready <none> 18h v1.19.9-gke.1400 xx.xx.xx.xx xx.xx.xx.xx Container-Optimized OS from Google 5.4.89+ containerd://1.4.3
pool-persistentのノードだけになった。
Cloud Load Balancingを使用せずに80,443番ポートを公開する
前述の通り、NodePortでは--service-node-port-rangeで設定された範囲でしかポートを公開できない。GKEではこのオプションを編集できそうにないので、公開できるポートの範囲は下記のとおりになる。
code:console
The Service "ingress-nginx-controller" is invalid: spec.ports0.nodePort: Invalid value: 80: provided port is not in the valid range. The range of valid ports is 30000-32767 GKEでできないのなら、80,443番へのリクエストを30080,30443番へポートフォワーディングすればいいのではないか。
GKEのノードでホストOSとして使用される、Container-Optimized OS from GoogleはiptablesでNATの設定をしているようなので、これを変更してみる。
まず、ホストVMにssh接続して、設定されているフィルター、NATルールを確認する。
code:console
$ gcloud compute ssh --zone "us-west1-c" "gke-org-chimata-pool-persistent-fe56b623-t18g" --project "org-chimata"
$ sudo iptables -nL
Chain INPUT (policy DROP)
target prot opt source destination
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
KUBE-EXTERNAL-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes externally-vis
ible service portals */
KUBE-FIREWALL all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT sctp -- 0.0.0.0/0 0.0.0.0/0
Chain FORWARD (policy DROP)
target prot opt source destination
KUBE-FORWARD all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT sctp -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy DROP)
target prot opt source destination
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
KUBE-FIREWALL all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state NEW,RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
Chain INPUTでポート22番だけホストで受けるようになっている?
その下の全部許可してるように見えるのはループバック用のポリシーか。→ -vオプションで確認できる。
code:console
$ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
KUBE-POSTROUTING all -- anywhere anywhere /* kubernetes postrouting rules */
IP-MASQ all -- anywhere anywhere /* ip-masq: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ chain */ ADDRTYPE match dst-type !LOCAL
:
:
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 127.0.0.0/8 anywhere /* ingress-nginx/ingress-nginx-controller:http */ tcp dpt:30080
KUBE-XLB-CG5I4G2RS3ZVWGLK tcp -- anywhere anywhere /* ingress-nginx/ingress-nginx-controller:http */ tcp dpt:30080
KUBE-MARK-MASQ tcp -- 127.0.0.0/8 anywhere /* ingress-nginx/ingress-nginx-controller:https */ tcp dpt:30443
KUBE-XLB-EDNDUDH2C75GIR6O tcp -- anywhere anywhere /* ingress-nginx/ingress-nginx-controller:https */ tcp dpt:30443
KUBE-MARK-MASQ tcp -- anywhere anywhere /* kube-system/default-http-backend:http */ tcp dpt:32428
KUBE-SVC-XP4WJ6VSLGWALMW5 tcp -- anywhere anywhere /* kube-system/default-http-backend:http */ tcp dpt:32428
Chain KUBE-POSTROUTING (1 references)
target prot opt source destination
RETURN all -- anywhere anywhere mark match ! 0x4000/0x4000
MARK all -- anywhere anywhere MARK xor 0x4000
MASQUERADE all -- anywhere anywhere /* kubernetes service traffic requiring SNAT */ random-fully
:
:
関係しそうなところだけ抜粋。k8sクラスターのhttpサーバーにIPマスカレードされている?
Node, Podのネットワークが何となく分かる資料は下記。
リダイレクトが全然できないので踏み台サーバーを置くことにした。以下挫折の記録。
code:console
$ sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
$ sudo python -m http.server 8080
### 外からアクセス: localhost:80 -> localhost:8080 への転送は通る
$ curl -L yy.yy.yy.yy
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
:
:
$ sudo iptables -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 30080
### 外からアクセス: localhost:80 -> docker:30080 への転送は失敗(何も返ってこない)
$ curl -L yy.yy.yy.yy
おそらくPREROUTINGのREDIRECT定義にマッチした時点でルールの適用が終了するので、KUBE-SERVICESのチェインを処理しないためにk8sクラスターのサービスまでパケットが届いていない。
ポートを書き換えつつ非終了なルールを書くにはどうしたらいいんでしょうか。
https://gyazo.com/953d99f7c77a2d970732fd943d754c93
仕方がないので踏み台サーバーを建てる。us-west1リージョンならf1-microが無料。
code:console
$ gcloud compute instances create "gke-springboard" --image-project "cos-cloud" --image-family "cos-stable" --zone "us-west1-c" --machine-type "f1-micro"
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
gke-springboard us-west1-c f1-micro xx.xx.xx.xx xx.xx.xx.xx RUNNING
踏み台がxx.xx.xx.xx、k8sノードがyy.yy.yy.yy。
建てたらiptablesを設定していく。
code:console
$ gcloud compute ssh --zone "us-west1-c" "gke-springboard" --project "org-chimata"
$ sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination yy.yy.yy.yy:30080
$ sudo iptables -t nat -I PREROUTING 2 -p tcp --dport 443 -j DNAT --to-destination yy.yy.yy.yy:30443
$ sudo iptables -t nat -I POSTROUTING -p tcp -d yy.yy.yy.yy -j SNAT --to-source xx.xx.xx.xx
$ sudo iptables -t filter -I FORWARD -p tcp -d yy.yy.yy.yy -j ACCEPT
$ sudo iptables -t filter -I FORWARD 2 -m state --state ESTABLISHED,RELATED -j ACCEPT
### 外からアクセス
$ curl -L xx.xx.xx.xx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
:
参考資料。
一般的にiptablesの設定は再起動すると消えるので、下記資料を参考に起動時に設定が行われるように調整する。
systemdのサービスタイプのコンフィグファイルを生成するように記述すればいいようだ。以下参考にした資料。
code:data/gke-springboard/cloud-config.cfg
write_files:
- path: /etc/systemd/system/config-port-forwarding.service
permissions: 0644
owner: root
content: |
Description=Configures port forwarding
Type=oneshot
RemainAfterExit=true
Environment=DESTINATION_IP=yy.yy.yy.yy
Environment=INTERNAL_IP=$(ip -o -4 addr list eth0 | awk '{ split($4,A,"/"); print A1 }') ExecStartPre=/sbin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination ${DESTINATION_IP}:30080
ExecStartPre=/sbin/iptables -t nat -I PREROUTING 2 -p tcp --dport 443 -j DNAT --to-destination ${DESTINATION_IP}:30443
ExecStartPre=/sbin/iptables -t nat -I POSTROUTING -p tcp -d $DESTINATION_IP -j SNAT --to-source $INTERNAL_IP
ExecStartPre=/sbin/iptables -t filter -I FORWARD -p tcp -d $DESTINATION_IP -j ACCEPT
ExecStart=/sbin/iptables -t filter -I FORWARD 2 -m state --state ESTABLISHED,RELATED -j ACCEPT
runcmd:
- systemctl daemon-reload
- systemctl config-port-forwarding.service
踏み台サーバーに配置して動作確認する。
code:console
$ gcloud compute scp --zone "us-west1-c" --project "org-chimata" ./data/gke-springboard/cloud-config.cfg gke-springboard:~/
$ gcloud compute ssh --zone "us-west1-c" "gke-springboard" --project "org-chimata"
### 権限が足りないので踏み台サーバーでrootになってファイル配置
$ sudo mv ~/cloud-config.cfg /etc/cloud/cloud.cfg.d/
$ sudo chown root:root /etc/cloud/cloud.cfg.d/cloud-config.cfg
$ exit
### 停止&再開してポートフォワーディングが生きてるか確認
$ gcloud compute instances stop gke-springboard --zone us-west1-c
$ gcloud compute instances start gke-springboard --zone us-west1-c
設定ファイル消えちゃった。下記を確認すると、ステートレスに構築できるよう/etc/などシステムの設定は再起動で消えるとのこと。
下記を参考にインスタンス作成時に--metadata-from-file user-dataオプションでcloud-configファイルを指定する。
code:console
$ gcloud compute instances delete gke-springboard --zone us-west1-c
$ gcloud compute instances create gke-springboard --image-project cos-cloud --image-family cos-stable --zone us-west1-c --machine-type f1-micro --metadata-from-file user-data=./data/gke-springboard/cloud-config.cfg
### 接続して確認
$ gcloud compute ssh gke-springboard --zone us-west1-c --project org-chimata
$ sudo iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 1 packets, 64 bytes)
pkts bytes target prot opt in out source destination
1 64 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
:
設定されていない。systemctl statusで確認する。
code:console
$ sudo systemctl status config-port-forwarding.service
● config-port-forwarding.service - Configures port forwarding
Loaded: loaded (/etc/systemd/system/config-port-forwarding.service; static; vendor preset: disabled)
Active: failed (Result: exit-code) since Tue 2021-05-18 19:31:42 UTC; 6s ago
Process: 908 ExecStartPre=/sbin/iptables -t nat -I POSTROUTING -p tcp -d $DESTINATION_IP -j SNAT --to-source $INTERNAL_IP (code=exited, status=2)
Process: 907 ExecStartPre=/sbin/iptables -t nat -I PREROUTING -p tcp --dport 443 -j DNAT --to-destination ${DESTINATION_IP}:30443 (code=exited, status=0/SUCCESS)
Process: 906 ExecStartPre=/sbin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination ${DESTINATION_IP}:30080 (code=exited, status=0/SUCCESS)
CPU: 4ms
May 18 19:31:42 gke-springboard systemd1: Starting Configures port forwarding... May 18 19:31:42 gke-springboard iptables908: iptables v1.8.5 (legacy): Bad IP address "$(ip" May 18 19:31:42 gke-springboard iptables908: Try `iptables -h' or 'iptables --help' for more information. May 18 19:31:42 gke-springboard systemd1: config-port-forwarding.service: Control process exited, code=exited status=2 May 18 19:31:42 gke-springboard systemd1: config-port-forwarding.service: Failed with result 'exit-code'. May 18 19:31:42 gke-springboard systemd1: Failed to start Configures port forwarding. May 18 19:31:42 gke-springboard systemd1: config-port-forwarding.service: Consumed 4ms CPU time systemd unitのEnvironmentは変数展開できない模様。
下記参考に環境変数ファイルを生成するスクリプトを用意して実行してみる。
code:data/gke-springboard/cloud-config.cfg
write_files:
- path: /etc/systemd/make-env-file.sh
permissions: 0755
owner: root
content: |
# for config-port-forwarding.service
echo DESTINATION_IP=yy.yy.yy.yy > /etc/systemd/config-port-forwarding.env
echo INTERNAL_IP=$(ip -o -4 addr list eth0 | awk '{ split($4,A,"/"); print A1 }') >> /etc/systemd/config-port-forwarding.env - path: /etc/systemd/system/make-env-file.service
permissions: 0644
owner: root
content: |
Description=Generates environment files for services
Type=oneshot
RemainAfterExit=true
ExecStart=/etc/systemd/make-env-file.sh
- path: /etc/systemd/system/config-port-forwarding.service
permissions: 0644
owner: root
content: |
Description=Configures port forwarding
After=make-env-file.service
Wants=make-env-file.service
Type=oneshot
RemainAfterExit=true
EnvironmentFile=/etc/systemd/config-port-forwarding.env
ExecStartPre=/sbin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to-destination ${DESTINATION_IP}:30080
ExecStartPre=/sbin/iptables -t nat -I PREROUTING 2 -p tcp --dport 443 -j DNAT --to-destination ${DESTINATION_IP}:30443
ExecStartPre=/sbin/iptables -t nat -I POSTROUTING -p tcp -d $DESTINATION_IP -j SNAT --to-source $INTERNAL_IP
ExecStartPre=/sbin/iptables -t filter -I FORWARD -p tcp -d $DESTINATION_IP -j ACCEPT
ExecStart=/sbin/iptables -t filter -I FORWARD 2 -m state --state ESTABLISHED,RELATED -j ACCEPT
runcmd:
- systemctl daemon-reload
- systemctl start config-port-forwarding.service
systemdのサービスから直接ファイルの書き込みができないようなので、シェルスクリプト(/etc/systemd/make-env-file.sh)を作成してそれを呼び出すサービスを用意、config-port-forwarding.serviceの前提サービスとした。
踏み台のインスタンスを再作成して動作確認。
code:console
$ gcloud compute instances delete gke-springboard --zone us-west1-c
$ gcloud compute instances create gke-springboard --image-project cos-cloud --image-family cos-stable --zone us-west1-c --machine-type f1-micro --metadata-from-file user-data=./data/gke-springboard/cloud-config.cfg
### 接続して確認
$ gcloud compute ssh gke-springboard --zone us-west1-c --project org-chimata
$ sudo iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 1 packets, 64 bytes)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:yy.yy.yy.yy:30080
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:yy.yy.yy.yy:30443
:
$ sudo iptables -t nat -vnL
:
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- * * 0.0.0.0/0 10.138.0.25
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
:
### 外からアクセス
$ curl -L xx.xx.xx.xx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
:
はいうまくいった。
TODO: cart-manager