はじめに
注意:今回作る環境は,初心者が学習用に作成したものです.
決して本番環境等,外部に公開するような重要な環境ではそのままの手順通りで行わないで下さい.
また,insecureな設定やベストプラクティスから外れた運用をしている箇所があります.
学習用Kubernetes環境を作るの第2回です.
Part 2 学習用Kubernetes環境を作る2 動的PVが使えない環境でKubernetes管理WebUIのPortainer環境を作る/データ永続化/NodePortについて
Kubernetes管理WebUIのPortainerをNodePortで公開,PersistentVolume(PV)のlocalでデータ永続化を行いHelmでデプロイします.
前提となるNodePortやデータ永続化の話は雑にざっくりとしますが,詳しくは他の記事を調べて下さい.
Kubernetesの環境は,前回構築した環境を前提としています.
NodePortについて
Kubernetesで,ホストの外部にサービスを公開する方法は,NodePort/LoadBalancer/Ingressの3つがあります.
この内,NodePortとLoadBalancerはサービスタイプであり,Ingressはサービスの外側で動きます.
NodePort
NodePortは,30000~32767番号を割り当てることができ,デプロイされたPodがあるノードで指定されたポート番号がリッスンされます.
マルチノードでスケジューラー等によりPodが別のノードにデプロイされた場合,アクセスするためのIPアドレスが変更しなければならない,負荷分散し辛い等のデメリットがあります.
接続方法はデプロイされたPodがあるノードのIP(またはDNS名):ポート番号です.おそらく最も,簡単な公開方法であり,Dockerのように利用出来ます.
LoadBalancer
LoadBalancerは,Kubernetesで最も標準的な公開方法です.クラウド環境では簡単に利用でき,ハンズオン等でも出てきます.オンプレな環境では,MetalLBがよく利用されます.サービスに対してExternal IPが割り当てられ,そのIPアドレスを利用して外部から接続が可能になります.
Ingress
Ingressはサービスタイプではなく,全サービスの外側で動きます.様々なコントローラーがあり,コントローラーにより動作が異なります.
Ingressには,公開する複数のサービスを,パスでマッピングする事で公開可能になります.
公開するサービスのPod内も,アクセスのために指定されたパスと同じパスで転送されるので,Podもそれに合わせて変更する必要があるというデメリットがあります.
コントローラー自体にも,サービスが存在し,このサービスの公開方法をNodePort/LoadBalancer等にすることによってアクセス方法が異なります.
NodePortのサンプルを動かす
サンプルのサービスをNodePortでデプロイします.
以下のファイルをtest.ymlとして保存します.
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: nginx
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: test-service
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 32767
protocol: TCP
selector:
app: test
以下のコマンドでファイルの内容をデプロイします.
kubectl apply -f test.yml
以下のコマンドで,deploymentによりデプロイされたPodが正常に動作をしているか確認します.
kubectl get pod
以下のように,test-deploymentから始まるPodのSTATUSがRunningであれば,正常にPodが稼働しています.
NAME READY STATUS RESTARTS AGE
test-deployment-5f6778868d-4zw82 1/1 Running 0 3m32s
次に,サービスを確認します.
kubectl get svc test-service
以下のような,結果になります.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
test-service NodePort 10.106.223.45 <none> 80:32767/TCP 14m
NodePortで設定したポート番号が割り当てられていることがわかります.
最後に実際に,ノードのIP:32767へ実際にブラウザでアクセスして確かめます.
アクセスして,上記のようになればOKです.
curlで確認する場合は,以下のようなコマンドになります.
curl ノードのIP:32767
以下のような結果になれば,OKです.
<!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
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
データ永続化について
Kubernetesでは様々なデータ永続化方法がありますが,今回はPersistentVolume(PV)のlocalを利用したデータ永続化を行います.
PVを利用するには,PV以外にもStorage Class(SC),Persistent Volume Claim(PVC)が必要です.
それぞれ何かを説明するよりもサンプルを出した方が早いと思うのでサンプルを出して説明します.
以下のファイルをtest-pv.ymlとして保存します.
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: test-sc
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: test-sc
local:
path: /mnt/kubernetes/test
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ノード名
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: test-sc
resources:
requests:
storage: 1Gi
先程作成したtest.ymlのdeploymentを以下のように変えます.
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: test-data
volumes:
- name: test-data
persistentVolumeClaim:
claimName: test-pvc
PVCは本来どちらかというとtest.yml側に書くべきですが,都合上こうしています.
サンプルを見ればわかりますが,構造としては,アプリケーション(Deployment)側が,PVCを割り当て,PVCはSCにPVを要求し,SCは要求(PVC)に対して最適なPVを与え,PVはデータを保存する実体を指すものです.
PVは,動的プロビジョニングにより自動生成する方が一般的なようですが,今回採用したlocalでは動的プロビジョニングをサポートしていないため,手動で作成する必要があります.
オンプレ環境では,local以外だと動的プロビジョニングが利用可能なNFS等を使うことになると思いますが,面倒なので一番楽なlocalを使用します.
また,localの制限として他には,1つのPVに対して1つのPodにしか割り当て出来ず,アクセスモードは常にReadWriteOnce固定であるということです.
このため,kindをPodにして使うか,DeploymentとStatefulSetではreplicasを1にして使わないといけません.
尚,DeploymentでPodを削除して,Podが再作成された場合は,特に問題なく動きます.
なぜ,このような回りくどい割り当て方をしているのかは,Kubernetes管理者とアプリケーション開発者を考えるとわかりやすいです.
5GBのPVをPodに割り当てることを考えるとこうなります.
このように抽象化していれば,管理者はSCと複数のPV(動的プロビジョニングが使えない環境等)を用意するだけでよく,開発者はデータ永続化をしたいアプリケーションがあれば,それをSCに要求するPVCとアプリケーションに永続化するPVCの設定をするだけでいいのです.
そうすれば,要求された容量以上で一番近い容量のPVをSCが自動的に割り当ててくれます.
では,applyにして実際にデプロイします.
まずは,実体のディレクトリを作成します.
sudo mkdir -p /mnt/kubernetes/test
sudo chmod 777 /mnt/kubernetes/test
test-pv.ymlをapplyします.
kubectl apply -f test-pv.yml
デプロイ出来たか確認します.
SCを確認します.
kubectl get sc test-sc
以下のようになっていれば,問題ありません.
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
test-sc kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 2m19s
PVを確認します.
kubectl get pv test-pv
以下のようになっていれば,問題ありません.
STATUSはAvailableです.
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv 1Gi RWO Retain Available default/test-pvc test-sc 24m
PVCを確認します.
kubectl get pvc test-pvc
STATUSはPendingです.
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Pending test-sc 5m7s
test.ymlをapplyして更新します.
kubectl apply -f test.yml
Podが正常に再作成され動いているか確認します.
kubectl get pod
Pod名が変更されているか,AGEがリセットされているか確認します.
NAME READY STATUS RESTARTS AGE
test-deployment-56bddb69d5-rc7c9 1/1 Running 0 53s
PVCの状態を確認します.
kubectl get pvc test-pvc
PendingからBoundに変わりました.
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound test-pv 1Gi RWO test-sc 20m
PVの状態を確認します.
kubectl get pv test-pv
AvailableからBoundに変わりました.
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv 1Gi RWO Retain Bound default/test-pvc test-sc 24m
マウントされているか確認するために,実体のディレクトリ(/mnt/kubernetes/test)にindex.htmlを作成し,変化を確認します.
htmlファイルは以下のようにします.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>
<body>
Hello PV!!
</body>
</html>
実際にブラウザでアクセスして,Hello PV!!と表示されるか,curlコマンドで上記htmlと同じ結果が帰って来れば成功です.
テストが完了すれば,これらは不要なので,削除します.
kubectl delete -f test.yml
kubectl delete -f test-pv.yml
sudo rm -rf /mnt/kubernetes/test
Portainerをデプロイ
これらを理解したところで,本題のPortainerをデプロイします.
基本的に,公式の手順通りですが,公式の手順通りでは,この環境ではデプロイ出来ません.
公式では,デフォルトのSCと諸アクセス権限があれば良いというように書かれていますが,これは正確ではなく,動的プロビジョニングが有効なSCが必要です(指定すれば,デフォルトである必要もない).
動的プロビジョニングが有効でない場合は,10GB以上ののPVが必要です.
データの保存先を作成
上記を踏まえて,まずPortainerのデータを保存ディレクトリを作成します.
mkdir /mnt/kubernetes/portainer
chmod 777 /mnt/kubernetes/portainer
SC・PVのymlを作成
次に,SCとPVを作成します.
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: portainer-sc
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: portainer-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: portainer-sc
local:
path: /mnt/kubernetes/portainer
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ノード名
今回このSCは,Portainer専用としています.
必要であれば,名前を変えたり,PVと別ファイルに分けて下さい.
デフォルトSCを利用する場合
ここで,デフォルトのSCを利用したい場合,SCの部分を以下のようにするか,
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: portainer-sc
annotations: { "storageclass.kubernetes.io/is-default-class": "true" }
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
公式順のように,以下のようなコマンド等で,デフォルトSCにして下さい.
kubectl patch storageclass portainer-sc -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
以下のようなコマンドで,
kubectl get sc portainer-sc
以下のように,defaultとなっていれば問題ありません.
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
portainer-sc (default) kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 15s
SC・PVのymlをapply
先程作成したymlをデプロイします.
kubectl apply -f portainer-vol.yml
正常にデプロイできたか確認します.
SCを確認します.
kubectl get sc portainer-sc
以下のようになっていれば,問題ありません.
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
portainer-sc kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 2m19s
PVを確認します.
kubectl get pv portainer-pv
以下のようになっていれば,問題ありません.
STATUSはAvailableです.
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
portainer-pv 10Gi RWO Retain Available portainer/portainer portainer-sc 3m7s
Helmを利用してインストール
Helmリポジトリを追加してインストールします.
helm repo add portainer https://portainer.github.io/k8s/
helm install --create-namespace -n portainer portainer portainer/portainer --set persistence.storageClass=portainer-sc
デフォルトSCを使用する場合,--set persistence.storageClass=portainer-scは不要です.
また,service.typeでサービスタイプを指定できます.
Portainerはportainerのnamespaceにデプロイされます.
Podが正常に稼働しているか確認します.
kubectl -n portainer get pod
以下のように,READYが1/1で,STATUSがRunningであれば,問題ありません.
NAME READY STATUS RESTARTS AGE
portainer-59f798c579-pdhm9 1/1 Running 0 2m19s
一応,PVも確認します.
kubectl get pv portainer-pv
Availableが,Boundに変わりました.
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
portainer-pv 10Gi RWO Retain Bound portainer/portainer portainer-sc 4m7s
後は,
http://ノードIP(orDNS):30777
にブラウザでアクセスできれば,成功です.
アクセスすれば,アカウント作成->接続方法の選択(今回はKubernetes)->設定になると思います.
設定でmetrics-serverと連携すれば,リソースの監視等も強化出来ます.
Dockerも管理出来るので,Dockerを使っている人はTLSでDockerソケットを公開するなどして,エンドポイントに追加するといいと思います. (Dockerの管理UIとしての方が優秀)