这个边车集装箱如何到达这里(在Kubernetes)?

注意事项 佩雷夫 :通过这篇由道琼斯工程师Scott Rahner撰写的文章,我们继续介绍一系列材料,这些材料说明Kubernetes的工作方式,其基本组件如何工作,如何互连和使用。 这次,这是一个实用的注释,其中包含用于在Kubernetes中创建钩子的示例代码,作者以自动创建Sidecar容器为借口进行了演示。


(照片由互联网上的Gordon A. Maxwell拍摄。)

当我开始研究Sidecar容器和服务网格时,我需要了解关键机制的工作原理-自动插入Sidecar容器。 确实,如果您使用Istio或Consul之类的系统,则在通过应用程序部署容器时,已配置的Envoy容器会突然出现在其pod中(Conduit发生了类似情况,我们在年初就谈到了-大约Transl。) 。 什么啊 怎么了 所以我的研究开始了...

对于那些不知道的人来说,sidecar-container是一个部署在应用程序容器旁边的容器,以某种方式“帮助”该应用程序。 这种用法的一个示例是用于管理流量和结束TLS会话的代理,用于流日志和指标的容器,用于扫描安全问题的容器...想法是通过为每个应用程序使用单独的容器来将整个应用程序的各个方面与业务逻辑隔离开来。功能。

在继续之前,我将概述我的期望。 本文的目的不是要解释Docker,Kubernetes,服务网格等的复杂性和使用场景,而是要演示一种扩展这些技术功能的强大方法。 本文适用于那些已经熟悉这些技术的使用,或者至少已经阅读了很多有关这些技术的人。 要尝试实际操作,您将需要一台已配置Docker和Kubernetes的机器。 最简单的方法是https://docs.docker.com/docker-for-windows/kubernetes/ (适用于Mac的Docker的Windows手册)。 (请注意perev 。:作为Linux和* nix系统用户的替代,我们可以提供Minikube 。)

整体图片


首先,让我们看一下Kubernetes:


Kube Arch在CC BY 4.0下获得许可

当您打算将某些内容部署到Kubernetes时,必须将对象发送到kube-apiserver。 这通常是通过将参数或YAML文件传递给kubectl来完成的。 在这种情况下,API服务器需要经历几个阶段,然后才能将数据直接放入etcd中并安排相应的任务:



此顺序对于了解如何插入边车集装箱很重要。 特别是,您需要注意Admission Control ,在其中Kubernetes会在存储对象之前对其进行验证并在必要时对其进行修改(有关此步骤的更多详细信息,请参见本文中的“访问控制”一章-大约翻译) 。 Kubernetes还允许您注册可以执行用户定义的验证和突变的 Webhook

但是,创建和注册钩子的过程并不是那么简单且文档齐全。 我不得不花几天时间阅读和重新阅读文档,以及分析Istio和Consul代码。 当涉及到某些API响应的代码时,我花了至少半天时间进行随机试验和错误。

在取得成果之后,我认为不与所有人共享它是不公平的。 它既简单又有效。

代号


webhook这个名字不言自明-它是一个HTTP端点,实现了Kubernetes中定义的API。 您正在创建一个Kubernetes可以在处理Deployment之前调用的API服务器。 我不得不在这里处理困难,因为只有几个示例可用,其中一些只是Kubernetes的单元测试,另一些则隐藏在庞大的代码库中间……而所有都是用Go编写的。 但是我选择了一个更实惠的选项-Node.js:

const app = express(); app.use(bodyParser.json()); app.post('/mutate', (req, res) => { console.log(req.body) console.log(req.body.request.object) let adminResp = {response:{ allowed: true, patch: Buffer.from("[{ \"op\": \"add\", \"path\": \"/metadata/labels/foo\", \"value\": \"bar\" }]").toString('base64'), patchType: "JSONPatch", }} console.log(adminResp) res.send(adminResp) }) const server = https.createServer(options, app); 

index.js

API的路径-在这种情况下为/mutate mutate-可以是任意的(将来仅应与传递给Kubernetes的YAML相对应)。 对于他来说,重要的是要了解和理解从API服务器接收的JSON。 在这种情况下,我们不会从JSON中提取任何东西,但是在其他情况下可能会派上用场。 在上面的代码中,我们更新了JSON。 为此需要两件事:

  1. 学习和理解JSON补丁
  2. 将JSON Patch表达式正确转换为以base64编码的字节数组。

完成此操作后,您要做的就是通过一个非常简单的对象将响应传递给API服务器。 在这种情况下,我们将标签foo=bar添加foo=bar任何广告连播中。

部署方式


好吧,我们有一些代码可以接受来自Kubernetes API服务器的请求并对其进行响应,但是如何进行部署? 以及如何让Kubernetes重定向我们这些请求? 您可以将此类端点部署到可以访问Kubernetes API服务器的任何位置。 最简单的方法是将代码部署到Kubernetes集群本身,我们将在此示例中进行此操作。 我试图使示例尽可能简单,因此对于所有操作,我仅使用Docker和kubectl。 让我们开始创建一个将在其中运行代码的容器:

 FROM node:8 USER node WORKDIR /home/node COPY index.js . COPY package.json . RUN npm install #       TLS CMD node index.js 

Dockerfile

显然,这里的一切都很简单。 从节点获取社区映像,然后将代码放入其中。 现在您可以执行一个简单的汇编:

 docker build . -t localserver 

下一步是在Kubernetes中创建一个Deployment:

 apiVersion: apps/v1 kind: Deployment metadata: name: webhook-server spec: replicas: 1 selector: matchLabels: component: webhook-server template: metadata: labels: component: webhook-server spec: containers: - name: webhook-server imagePullPolicy: Never image: localserver 

deployment.yaml

注意我们如何暗示刚创建的图像? 它也可能是一个Pod,以及我们可以在Kubernetes中将服务连接到的其他东西。 现在定义此服务:

 apiVersion: v1 kind: Service metadata: name: webhook-service spec: ports: - port: 443 targetPort: 8443 selector: component: webhook-server 

因此,在Kubernetes中,将出现一个终结点,其内部名称指向我们的容器。 最后一步是告诉Kubernetes我们希望API服务器在准备进行突变时调用此服务:

 apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: webhook webhooks: - name: webhook-service.default.svc failurePolicy: Fail clientConfig: service: name: webhook-service namespace: default path: "/mutate" #    base64-  rootCA.crt #    `cat rootCA.crt | base64 | tr -d '\n'` #    .  caBundle: "==" rules: - operations: [ "CREATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] 
hook.yaml

这里的名称和路径可以是任何名称,但我试图使它们尽可能有意义。 更改路径意味着需要修改JavaScript中的相应代码。 Webhook failurePolicyfailurePolicy -它确定在挂钩返回错误或失败时是否应该保存对象。 在这种情况下,我们告诉Kubernetes不要继续处理。 最后,规则:它们将根据您期望Kubernetes采取的API调用而改变。 在这种情况下,由于我们正在尝试模拟Sidecar容器的插入,因此我们需要拦截创建Pod的请求。

仅此而已! 如此简单...但是安全性又如何呢? RBAC是本文未涉及的一个方面。 我假设您正在Minikube或Kubernetes中运行该示例,该示例随Docker for Windows / Mac一起提供。 但是,我将告诉您另一个必要的元素。 Kubernetes API服务器仅使用HTTPS访问端点,因此该应用程序需要SSL证书。 您还需要告诉Kubernetes根证书的证书颁发机构是谁。

TLS


仅出于演示目的(!!!),我向Dockerfile添加了一些代码以创建根CA并使用它来签署证书:

 RUN openssl genrsa -out rootCA.key 4096 RUN openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=*.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl genrsa -out webhook.key 4096 RUN openssl req -new -key webhook.key -out webhook.csr \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=webhook-service.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl x509 -req -in webhook.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out webhook.crt -days 1024 -sha256 RUN cat rootCA.crt | base64 | tr -d '\n' 

Dockerfile

请注意:最后一步是显示单行,其中根CA用base64编码。 这正是钩子配置所需要的,因此在您进一步的测试中,请确保将此行复制到caBundle文件的caBundle字段中。 Dockerfile将证书直接抛出到WORKDIR ,因此JavaScript只是从那里获取证书并将其用于服务器:

 const privateKey = fs.readFileSync('webhook.key').toString(); const certificate = fs.readFileSync('webhook.crt').toString(); //… const options = {key: privateKey, cert: certificate}; const server = https.createServer(options, app); 

现在该代码支持HTTPS启动,并且还告诉Kubernetes在哪里可以找到我们以及可以信任哪个信任中心。 剩下的只是将它们全部嵌入到集群中:

 kubectl create -f deployment.yaml kubectl create -f service.yaml kubectl create -f hook.yaml 

总结一下


  • Deployment.yaml启动一个容器,该容器通过HTTPS提供挂钩API,并返回JSON补丁以修改对象。
  • Service.yaml为容器提供了一个端点webhook-service.default.svc
  • Hook.yaml告诉API服务器在哪里找到我们: https://webhook-service.default.svc/mutate

让我们尝试业务!


一切都部署在集群中-是时候尝试运行代码了,我们将通过添加新的pod / Deployment来实现。 如果一切正常,钩子将不得不添加一个附加标签foo

 apiVersion: apps/v1 kind: Deployment metadata: name: test spec: replicas: 1 selector: matchLabels: component: test template: metadata: labels: component: test spec: containers: - name: test image: node:8 command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] 

test.yaml

 kubectl create -f test.yaml 

好的,我们看到已deployment.apps test createddeployment.apps test created ……但效果如何?

 kubectl describe pods test Name: test-6f79f9f8bd-r7tbd Namespace: default Node: docker-for-desktop/192.168.65.3 Start Time: Sat, 10 Nov 2018 16:08:47 -0500 Labels: component=test foo=bar 

太好了! 尽管为test.yaml提供了一个标签( component ),但是生成的pod收到两个: componentfoo

家庭作业


但是等等! 我们是否将使用此代码创建Sidecar容器? 我警告说,我将展示如何添加侧边车...现在,凭借所获得的知识和代码: https : //github.com/dowjones/k8s-webhook-大胆尝试并弄清楚如何制作自动插入的侧边车。 这很简单:您只需要准备正确的JSON补丁,它将在测试Deployment中添加一个额外的容器。 编排快乐!

译者的PS


另请参阅我们的博客:

Source: https://habr.com/ru/post/zh-CN431252/


All Articles