原创 吴就业 124 0 2023-06-03
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/1d93c9c4d4884537aec6dec50017e1f8
作者:吴就业
链接:https://wujiuye.com/article/1d93c9c4d4884537aec6dec50017e1f8
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
terraform-controller是一个实现kubevela整合terraform能力的插件,本质上也是一个Operator,核心能力就是提供自定义的Configuration资源定义+Configuration资源控制器。通过Configuration资源的Spec声明hcl代码,Configuration资源控制器监听Configuration资源的创建/更新/销毁,执行terraform apply
和terraform destroy
命令,实现申请、更新IaC资源(基础设施),和删除IaC资源。
terraform-controller的实现原理就是通过创建一个Job来启动一个Pod,在Pod中启动一个带terraform的镜像容器,来执行terraform apply
或terraform destroy
命令。在terraform-controller一章中我们会详细介绍terraform-controller的工作原理。
由于terraform-controller不支持配置Job的最大重试次数,如果terraform apply
执行失败,例如云平台账号没有足够的费用去申请IaC资源,Job的控制器会不断的拉起Pod重试。正常情况,这种重试并不会有什么副作用,在案例中,我们只需要给账号充值,重试还能使得自动恢复IaC的申请,完成应用的部署。
然而,在自研混合云平台的背景下,由于前期研发人员投入有限,很多IaC的申请,背后都是“人工智能”,就是通过提交工单的方式申请,而这种失败的重试,可能会导致提交非常多的工单,不仅是占用数据库资源,还会导致重要工单被这些无限重复的工单覆盖。
这个问题笔记已经向社区反馈(https://github.com/kubevela/terraform-controller/issues/361
),并提了PR,只是官方不会立即发布一个新的版本,而我们又不想基于terraform-controller二次开发,主要是后期如果社区更新支持新特性,就很难合并到自己的分支,因此我们想通过webhook去修改Job的最大重试次数。
Kubernetes apiserver提供两种特殊的准入控制器,分别是MutatingAdmissionWebhook和ValidatingAdmissionWebhook。这两种准入控制器,将发送准入请求到外部的HTTP回调服务,并接收准入响应。
准入控制器就是在对象持久化之前用于对 Kubernetes API Server 的请求进行拦截处理,在请求经过身份验证和授权之后放行通过。Mutating准入控制器可以修改被处理的资源对象,Validating准入控制器则不能修改。只要Mutating准入控制器和Validating准入控制器中的任何一个拒绝了请求,都会立即拒绝整个请求,并将错误返回给用户。
图片来源于官方文档?
webhook就是准入控制器的http回调服务。k8s提供两种类型的用于创建Webhook的资源定义:MutatingWebhookConfiguration和ValidatingWebhookConfiguration。
使用MutatingWebhookConfiguration资源注册的Webhook可接受或拒绝资源对象请求,并且可以变更资源对象。而使用ValidatingWebhookConfiguration资源注册的Webhook只可在不更改对象的情况下接受或拒绝对象请求,通常用于参数校验。
由于我们的需求是修改Job的最大重试次数,也就是需要修改Job资源对象,因此我们需要使用MutatingWebhookConfiguration资源来注册我们的webhook,并且需要开发webhook服务,提供一个http接口。
我们先基于kubebuilder脚手架创建一个空的Operator项目,基于这个空项目开发webhook。
1.声明MutatingWebhookConfiguration资源,注册一个webhook。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
creationTimestamp: null
name: terraform-job-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
caBundle: |-
##这里填自签证书的base64字符串
service:
name: terraform-job-webhook-service
namespace: vela-system
path: /modify-terraform-job
failurePolicy: Ignore
name: modify.terraform.job.kb.io
rules:
- apiGroups:
- "batch"
apiVersions:
- "v1"
operations:
- CREATE
- UPDATE
resources:
- jobs
sideEffects: None
注:如果是拦截非自定义资源的请求,建议通过标签选择过滤,或者将failurePolicy设置为Ignore,否则webhook服务故障可能会导致整个k8s集群瘫痪。
2.我们需要编写Service资源,将接口暴露给Kubernetes API Server调用。
---
apiVersion: v1
kind: Service
metadata:
name: terraform-job-webhook-service
namespace: vela-system
spec:
ports:
- name: webhook-port
port: 443
targetPort: 8090 ## webhook服务的端口
protocol: TCP
selector:
app: terraform-daemon
3.我们需要实现admission.Handler接口,这是controller-runtime封装好的API。
type terraformJobHandler struct {
decoder *admission.Decoder
client client.Client
}
func NewTerraformJobHandler(client client.Client) admission.Handler {
return &terraformJobHandler{
client: client,
}
}
func (s *terraformJobHandler) InjectDecoder(d *admission.Decoder) error {
s.decoder = d
return nil
}
func (s *terraformJobHandler) Handle(ctx context.Context, request admission.Request) admission.Response {
logger := log.FromContext(ctx)
job := &v1.Job{}
err := s.decoder.Decode(request, job)
if err != nil {
logger.Error(err, "decoder job error")
return admission.Errored(http.StatusBadRequest, err)
}
if !k8sutil.IsTerraformJob(job) {
return admission.Allowed("")
}
if *job.Spec.BackoffLimit > 0 {
bl := int32(0)
job.Spec.BackoffLimit = &bl
if patch, err := json.Marshal(job); err != nil {
logger.Error(err, fmt.Sprintf("marshal job %s/%s to json error"), job.Namespace, job.Name)
return admission.Errored(http.StatusInternalServerError, err)
} else {
logger.Info(fmt.Sprintf("update job %s/%s backoff limit 0", job.Namespace, job.Name))
return admission.PatchResponseFromRaw(request.Object.Raw, patch)
}
}
return admission.Allowed("")
}
4.我们需要自签证书,Kubernetes API Server在调用我们的webhook服务时会使用https调用。关于如何自签证书稍后会介绍。
5.在main方法中通过Manager给webhook服务注册接口处理器。
mgr.GetWebhookServer().Register("/modify-terraform-job",
&webhook.Admission{Handler: NewTerraformJobHandler(mgr.GetClient())})
6.在main方法中通过Manager给webhook服务配置证书的路径。
mgr.GetWebhookServer().CertDir = webhookCertPath
mgr.GetWebhookServer().CertName = "cert.pem"
mgr.GetWebhookServer().KeyName = "key.pem"
7.在main方法中通过Manager给webhook服务配置监听端口。
mgr.GetWebhookServer().Port = port
8.我们需要声明Deployment来部署我们的webhook服务。
apiVersion: apps/v1
kind: Deployment
metadata:
name: terraform-daemon
namespace: vela-system
labels:
app: terraform-daemon
spec:
selector:
matchLabels:
app: terraform-daemon
replicas: 1
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: terraform-daemon
labels:
app: terraform-daemon
spec:
containers:
- name: terraform-daemon
image: terraform-daemon:1.0.0
command:
- ./terraform-daemon
args:
- --webhook-bind-port=8090 ## webhook服务的端口
- --webhook-cert-path=/data/cert ## 自签ca证书的目录
这里推荐使用cfssl工具,使用起来比较简单。由于证书的制作不是本篇的重点,这里只推荐一个生成方式,只要按照教程一步步来做就可以制作出来。
1.安装cfssl工具,mac下使用brew安装。
brew install cfssl
2.创建一个目录,用于作为自签证书的workspace。
mkdir /tmp/ca
3.在workspace下添加以下几个配置文件。
ca-config.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"server": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}
ca-csr.json
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "CA",
"ST": "Oregon"
}
]
}
server-csr.json
{
"CN": "admission",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "Kubernetes",
"ST": "Oregon"
}
]
}
4.编写脚本
create_cert.sh
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=192.168.11.11 \
-profile=server \
server-csr.json | cfssljson -bare server
5.可修改ca-config.json配置文件设置过期时间,即修改signing.default.expiry
与signing.profiles.server.expiry
。
6.需要修改create_cert.sh脚本的hostname参数,hostname值为“{service-name}.{service-namespace}.svc
”,就是webhook的Service的名称和部署的namespace,如果是本地开发环境debug可以填自己的ip地址。
7.执行create_cert.sh脚本,我们将在workspace得到ca.pem、ca-key.pem、server.pem、server-key.pem,其中server.pem、server-key.pem需要我们在构建docker镜像的时候跟程序一起打包进去,并放在/data/cert
目录下(本案例),并更名为cert.pem、key.pem。
8.然后执行cat ca.pem | base64
命令,将命令输出的字符串copy,填入MutatingWebhookConfiguration资源的caBundle字段。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
通常申请基础设施,我们需要向运维描述我们需要什么基础设施、什么规格,运维根据我们的描述去检查是否已经申请过这样的资源,有就会直接给我们使用基础设施的信息,没有再帮我们申请,然后告诉我们使用基础设施的信息,例如mysql的jdbc和用户名、密码。如果将描述代码化,基础设施的申请自动化,就能实现“基础设施即代码”。而terraform就是实现“将描述代码化”的工具软件。
在Job场景,如果Job达到backoffLimit还是失败,而且backoffLimit值很小,很快就重试完,没能及时的获取到容器的日记。而达到backoffLimit后,job的controller会把pod删掉,这种情况就没办法获取pod的日记了,导致无法得知job执行失败的真正原因,只能看到job给的错误:"Job has reached the specified backoff limit"。
kubebuilder使用helm代替kustomize;代码改了但似乎没生效-镜像拉取问题; 使用ConfigMap替代Apollo配置中心的最少改动方案;环境变量的注入以及传递;Kubebuilder单测跑不起来;Helm chart和finalizer特性冲突问题。
新的云原生中间件很难短时间内覆盖到企业项目中,企业走云原生这条道路,还需要考虑传统中间件如何上云的问题。最需要解决的是如何容器化部署,以及自动化运维。这就不得不借助Operator了。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。