我在Pulumi (Python)中尝试使用Kubernetes部署Pods / Helm Chart
以下是一位Pulumi初学者在学习Kubernetes并进行评估时的笔记。由于官方文档以TypeScript为主,Python的示例较少,所以特别记录下Python的示例。
概要
如标题所述,我在Pulumi上试评了Kubernetes的部署。以下是我的主要感受。
-
- Kubernetes用のテンプレート(kubernetes-python)が提供されているため、比較的導入のしきいは低い(プロジェクトの初期状態からpulumi upする程度で、シンプルなnginxのdeploymentが作成できる)。
kubectlコマンドが叩ける状態になっていれば、特に追加の設定はなさそう。
PulumiのHelm Chartデプロイ機能は、単純にHelmをKickするだけでなく、独自にServideやDeploymentなどのリソースを展開&解釈してデプロイしている。このため、Helm 2.xでもTiller Serverが不要。
今回はHelm 3.xを利用したためこの恩恵は受けられず、逆にhelm listコマンドで内容を確認できないデメリットもあり。
Pulumi.Output(生成されたリソースの出力用パラメータ)の変換実装には、相応に試行錯誤が必要だった(apply関数の周りなどの理解がある程度難しい)。
実装コードは逐次処理のPythonコードに見えるが、内部的には(PreviewやDiff制御など)宣言的な概念も扱っているため、若干理解し辛い側面もありそう。
评价
假设
假设Kubernetes集群(使用AWS EKS)已经构建完成,并且Plumi和Helm的初始设置也已经完成。
测试所使用的Helm Chart(bitnami/jenkins)的仓库也已经预先注册(虽然可以通过FetchOps类的功能从仓库中获取,但本次评估的目标将其排除在外)。
$ pulumi version
v1.8.1
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-0-145.ap-northeast-1.compute.internal Ready <none> 1h51m v1.14.7-eks-1861c5
ip-10-0-2-96.ap-northeast-1.compute.internal Ready <none> 1h50m v1.14.7-eks-1861c5
$ helm version
version.BuildInfo{Version:"v3.0.2", GitCommit:"19e47ee3283ae98139d98460de796c1be1e3975f", GitTreeState:"clean", GoVersion:"go1.13.5"}
$ helm repo list
NAME URL
bitnami https://charts.bitnami.com/bitnami
项目初始化
使用kubernetes-python模板,创建一个新的Pulumi项目(使用默认回答回答问题)。
$ mkdir python-k8s && cd python-k8s
$ pulumi new kubernetes-python
This command will walk you through creating a new Pulumi project.
Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.
project name: (python-k8s)
project description: (A minimal Kubernetes Python Pulumi program)
Created project 'python-k8s'
Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack 'dev'
Your new project is ready to go! ✨
To perform an initial deployment, run the following commands:
1. virtualenv -p python3 venv
2. source venv/bin/activate
3. pip3 install -r requirements.txt
Then, run 'pulumi up'
按照消息所显示的,进行Python虚拟环境的配置(以及安装依赖库)。
$ pip3 install virtualenv
$ virtualenv -p python3 venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt
实施
将Python的实现代码(__main__.py)更改为创建以下两种资源。请注意,Output字段应定义为输出每个服务的主机名(分配给AWS Elastic Load Balancer的主机名)。
-
- NGINX Deployment / Service
NGINXのdeploymentと、外部公開用のLoadBalancer TypeのService。
Jenkins Helm Chart
bitnami/jenkinsのHelm Chart。
import base64
import pulumi
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Service
from pulumi_kubernetes.helm.v2 import Chart, ChartOpts
def deploy_nginx_service():
app_name = "nginx"
app_labels = { "app": app_name }
nginx_deployment = Deployment(
app_name,
spec={
"selector": { "match_labels": app_labels },
"replicas": 1,
"template": {
"metadata": { "labels": app_labels },
"spec": { "containers": [{ "name": app_name, "image": "nginx" }] }
}
})
nginx_service = Service(
app_name,
metadata={
"labels": nginx_deployment.spec["template"]["metadata"]["labels"],
},
spec={
"type": "LoadBalancer",
"ports": [{ "port": 80, "target_port": 80, "protocol": "TCP" }],
"selector": app_labels,
})
return nginx_service
def deploy_jenkins_chart():
return Chart("jenkins", ChartOpts(
chart="jenkins",
repo="bitnami",
values={},
))
nginx_service = deploy_nginx_service()
jenkins_chart = deploy_jenkins_chart()
pulumi.export("nginx_hostname",
nginx_service.status.apply(
lambda x: x["load_balancer"]["ingress"][0]["hostname"]))
pulumi.export("jenkins_hostname",
jenkins_chart.resources.apply(
lambda x: x["v1/Service:jenkins"].status["load_balancer"]["ingress"][0]["hostname"]))
执行
pulumi up
Previewing update (dev):
Type Name Plan
+ pulumi:pulumi:Stack python-k8s-dev create
+ ├─ kubernetes:helm.sh:Chart jenkins create
+ │ ├─ kubernetes:core:PersistentVolumeClaim jenkins create
+ │ ├─ kubernetes:core:Secret jenkins create
+ │ ├─ kubernetes:core:Service jenkins create
+ │ └─ kubernetes:apps:Deployment jenkins create
+ ├─ kubernetes:apps:Deployment nginx create
+ └─ kubernetes:core:Service nginx create
Resources:
+ 8 to create
Do you want to perform this update? yes
Updating (dev):
Type Name Status
+ pulumi:pulumi:Stack python-k8s-dev created
+ ├─ kubernetes:helm.sh:Chart jenkins created
+ │ ├─ kubernetes:core:Secret jenkins created
+ │ ├─ kubernetes:core:Service jenkins created
+ │ ├─ kubernetes:core:PersistentVolumeClaim jenkins created
+ │ └─ kubernetes:apps:Deployment jenkins created
+ ├─ kubernetes:apps:Deployment nginx created
+ └─ kubernetes:core:Service nginx created
Outputs:
jenkins_hostname: "xxx.ap-northeast-1.elb.amazonaws.com"
nginx_hostname : "yyy.ap-northeast-1.elb.amazonaws.com"
Resources:
+ 8 created
根据Outputs中输出的主机名,分别对应jenkins和nginx的服务(负载均衡器)。当使用curl访问时,每个服务都会返回响应。
$ curl -s --head xxx.ap-northeast-1.elb.amazonaws.com | grep X-Jenkins:
X-Jenkins: 2.204.1
$ curl -s --head yyy.ap-northeast-1.elb.amazonaws.com | grep Server
Server: nginx/1.17.6
资源确认
通过使用`kubectl`命令,您可以确认生成的资源。然而,由于Pulumi对Helm Chart进行了自定义展开,因此不会显示(列表为空)。
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
jenkins 1/1 1 1 32m
nginx-nziiq5rs 1/1 1 1 32m
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins LoadBalancer 172.20.125.15 xxx.ap-northeast-1.elb.amazonaws.com 80:32525/TCP,443:31321/TCP 33m
kubernetes ClusterIP 172.20.0.1 <none> 443/TCP 1h42m
nginx-6hbjq6d7 LoadBalancer 172.20.14.82 yyy.ap-northeast-1.elb.amazonaws.com 80:32325/TCP 33m
$ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
以下是一份参考资料。
- Pulumi Kubernetes
Kubernetes的插件说明。
- Docs Reference API pulumi_kubernetes
API参考文档。
- Pulumi Kubernetes WordPress Helm Chart
在本教程中,我们将使用 @pulumi/kubernetes 的Helm API将v2.1.3版本的Wordpress Helm Chart部署到一个Kubernetes集群中。不需要安装Tiller服务器。Pulumi将展开Helm Chart并将展开的YAML提交到集群中。
Pulumi的Helm提供程序通过独自展开和部署Chart来解释。由于这个原因,helm list命令的结果中没有任何输出。
- Docs – Intro to Pulumi – Architecture & Concepts – Programming Model – apply
对于Output的输出转换的解释。
在对Pulumi输出的Output(Pulumi.Output类型)进行处理时,需要使用apply函数(可能在内部进行了特殊处理,或者简单地进行对象转换可能导致无法正常工作。例如,可能会遇到阻塞pulumi up等处理,导致处理无法继续进行的现象)。
apply 方法接受一个回调函数,当 Output 可用时,回调函数将被传递 Output 的值,并返回新的值。apply 被调用后的结果是一个新的 Output,其值是从回调函数返回的值,并包括原始 Output 的依赖关系。如果回调函数本身返回一个 Output,则该输出的依赖关系将与返回的 Output 的依赖关系合并。