Rego 不好用?用 Pipy 實現 OPA
還不知道 Pipy 是什麼的同學可以看下 GitHub[1] 。
Pipy 是一個輕量級、高性能、高穩定、可編程的網絡代理。Pipy 核心框架使用 C++ 開發,網絡 IO 採用 ASIO 庫。Pipy 的可執行文件僅有 5M 左右,運行期的內存佔用 10M 左右,因此 Pipy 非常適合做 Sidecar proxy。
Pipy 內置了自研的 pjs 作爲腳本擴展,使得 Pipy 可以用 JS 腳本根據特定需求快速定製邏輯與功能。
Pipy 採用了模塊化、鏈式的處理架構,用順序執行的模塊來對網絡數據塊進行處理。這種簡單的架構使得 Pipy 底層簡單可靠,同時具備了動態編排流量的能力,兼顧了簡單和靈活。通過使用 REUSE_PORT 的機制(主流 Linux 和 BSD 版本都支持該功能),Pipy 可以以多進程模式運行,使得 Pipy 不僅適用於 Sidecar 模式,也適用於大規模的流量處理場景。在實踐中,Pipy 獨立部署的時候用作 “軟負載”,可以在低延遲的情況下,實現媲美硬件的負載均衡吞吐能力,同時具有靈活的擴展性。
在玩過幾次 Pipy 並探究其工作原理後,又有了更多的想法。
• 初探可編程網關 Pipy• 可編程網關 Pipy 第二彈:編程實現 Metrics 及源碼解讀 • 可編程網關 Pipy 第三彈:事件模型設計
在使用 OPA 的時候,一直覺得 Rego 不是那麼順手,使用 pipy js 來寫規則的想法油然而生。今天就一起試試這個思路。果然,不試不知道,一試發現太多的驚喜~Pipy 不止於 “代理”,更有很多可以適用的場景:
• 極小的單一可執行文件(single binary)使得 pipy 可能是最好的 “雲原生 sidecar”•sidecar 不僅僅是代理,還可以做控制器,做運算單元 •proxy 的串路結構適合各種管控類的操作,比如訪問控制 •Pipy js 的擴展能力和快速編程能力,很適合做 “規則引擎”,或者用最近流行的說法 “雲原生的規則引擎”。對比 OPA 我認爲它完全夠格做一個 “羽量級規則執行引擎”
現在我更傾向於定義 pipy 是一個 “雲原生的流量編程框架”,代理只是其底層的核心能力,疊加了 pipy js 以後,上層可以做的事情很多,“流量滋養萬物”。
在 使用 Open Policy Agent 實現可信鏡像倉庫檢查 之後,就在想 Pipy 是否一樣可以做到,將內核替換成 Pipy + 規則。所以今天大部分內容和上面這篇是相似的。
來,一起看看這個 “不務正業” 的 Pipy 如何實現 Kubernetes 的准入控制器 來做鏡像的檢查。
環境
繼續使用 minikube
minikube start
創建部署 Pipy 的命名空間
kubectl create namespace pipy
kubens pipy
kubectl label ns pipy pipy/webhook=ignore #後面解釋
規則
在 OPA 中,通過 kube-mgmt
容器監控 configmap
的改動,將 Policy 推送到同 pod 的 opa 容器中。
對於 Pipy 爲了漸變,直接使用掛載的方式將保存了規則的 configmap
掛載到 Pipy 的容器中。
實際的使用中,Pipy 支持輪訓的方式檢查控制平面中規則的變更,並實時加載;也可以實現與 OPA 的 kube-mgmt 同樣的邏輯。
實現了上一講功能 [2] 的 pipy 規則如下:
cat > pipy-rule.js <<EOF
pipy({
_repoPrefix: '192.168.64.1', //192.168.64.1:5000 是筆者本地容器運行的一個私有倉庫。
_tagSuffix: ':latest',
})
.listen(6443, {
tls: {
cert: os.readFile('/certs/tls.crt').toString(),
key: os.readFile('/certs/tls.key').toString(),
},
})
.decodeHttpRequest()
.replaceMessage(
msg => (
((req, result, invalids, reason) => (
req = JSON.decode(msg.body),
invalids = req.request.object.spec.containers.find(container => (
(!container.image.startsWith(_repoPrefix) ? (
reason = `${container.image} repo not start with ${_repoPrefix}`,
console.log(reason),
true
) : (false))
||
(container.image.endsWith(_tagSuffix) ? (
reason = `${container.image} tag end with ${_tagSuffix}`,
console.log(reason),
true
) : (false)
))),
invalids != undefined ? (
result = {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": {
"allowed": false,
"uid": req.request.uid,
"status": {
"reason": reason,
},
},
}
) : (
result = {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": {
"allowed": true,
"uid": req.request.uid
},
}
),
console.log(JSON.encode(result)),
new Message({
'status' : 200,
'headers': {
'Content-Type': 'application/json'
}
}, JSON.encode(result))
))()
)
)
.encodeHttpResponse()
EOF
將規則保存在 configmap 中:
kubectl create configmap pipy-rule --from-file=pipy-rule.js
在 Kubernetes 上部署 Pipy
Kubernetes 與准入控制器(Admission Controller[3])的通信需要使用 TLS。配置 TLS,使用 openssl
創建證書頒發機構(certificate authority CA)和 OPA 的證書 / 祕鑰對。
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca"
爲 OPA 創建 TLS 祕鑰和證書:
cat >server.conf <<EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
CN = pipy.pipy.svc
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = pipy.pipy.svc
EOF
注意
CN
和alt_names
必須與後面創建 Pipy service 的匹配。
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -config server.conf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf
爲 OPA 創建保存 TLS 憑證的 Secret:
kubectl create secret tls pipy-server --cert=server.crt --key=server.key
將 Pipy 部署爲準入控制器(admission controller)。爲了方便調試,我們使用啓動 Pipy 的時候打開了控制檯。
kind: Service
apiVersion: v1
metadata:
name: pipy
namespace: pipy
spec:
selector:
app: pipy
ports:
- name: https
protocol: TCP
port: 443
targetPort: 6443
- name: gui # 方便調試
protocol: TCP
port: 6060
targetPort: 6060
- name: http
protocol: TCP
port: 6080
targetPort: 6080
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: pipy
namespace: pipy
name: pipy
spec:
replicas: 1
selector:
matchLabels:
app: pipy
template:
metadata:
labels:
app: pipy
name: pipy
spec:
containers:
- name: pipy
image: pipy:latest
imagePullPolicy: IfNotPresent
args:
- "pipy"
- "/opt/data/pipy-rule.js"
- "--gui-port=6060" # 方便調試
# - "--log-level=debug"
ports:
- name: gui
containerPort: 6060
protocol: TCP
- name: http
containerPort: 6080
protocol: TCP
- name: https
containerPort: 6443
protocol: TCP
volumeMounts:
- readOnly: true
mountPath: /certs
name: pipy-server
- readOnly: false
mountPath: /opt/data
name: pipy-rule
volumes:
- name: pipy-server
secret:
secretName: pipy-server
- name: pipy-rule
configMap:
name: pipy-rule
暴露控制檯的訪問:
kubectl expose deploy pipy --name pipy-node --type NodePort
kubectl get svc pipy-port
minikube service --url pipy-node -n pipy
# 找到控制檯端口
接下來,生成將用於將 Pipy 註冊爲準入控制器的 manifest。
cat > webhook-configuration.yaml <<EOF
kind: ValidatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1beta1
metadata:
name: pipy-validating-webhook
webhooks:
- name: validating-webhook.pipy.flomesh-io.cn
namespaceSelector:
matchExpressions:
- key: pipy/webhook
operator: NotIn
values:
- ignore
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["pods"]
clientConfig:
caBundle: $(cat ca.crt | base64 | tr -d '\n')
service:
namespace: pipy
name: pipy
EOF
生成的配置文件包含 CA 證書的 base64 編碼,以便可以在 Kubernetes API 服務器和 OPA 之間建立 TLS 連接。
kubectl apply -f webhook-configuration.yaml
測試
pod-bad-repo.yaml
:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web-server
name: web-server
namespace: default
spec:
containers:
- image: nginx:1.21.1
name: web-server
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
kubectl apply -f pod-bad-repo.yaml
Error from server (nginx:1.21.1 repo not start with 192.168.64.1): error when creating "pod-bad-repo.yaml": admission webhook "validating-webhook.pipy.flomesh-io.cn" denied the request: nginx:1.21.1 repo not start with 192.168.64.1
pod-bad-tag.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web-server
name: web-server
namespace: default
spec:
containers:
- image: 192.168.64.1:5000/nginx:latest
name: web-server
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
kubectl apply -f pod-bad-tag.yaml
Error from server (192.168.64.1:5000/nginx:latest tag end with :latest): error when creating "pod-bad-tag.yaml": admission webhook "validating-webhook.pipy.flomesh-io.cn" denied the request: 192.168.64.1:5000/nginx:latest tag end with :latest
pod-ok.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web-server
name: web-server
namespace: default
spec:
containers:
- image: 192.168.64.1:5000/nginx:1.21.1
name: web-server
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
kubectl apply -f pod-ok.yaml
pod/web-server created
總結
OPA 哪哪都好,唯一缺點就是其引進的 Rego
語言抬高了使用的門檻。而 Pipy 的規則是通過 JavaScrip 來編寫的,前端的同學一樣可以完成規則的編寫。完全替代可能誇張了一些,但確實在部分場景下可以替代 OPA。
玩到這裏,你會發現有了規則,加上功能強大的過濾器(現在我喜歡叫他們 Hook 了),Pipy 的可玩性非常強。
比如 OPA: Kubernetes 准入控制策略 Top 5,比如...。大膽的想象吧。
想寫一個系列,就叫 “如何把 Pipy 玩壞”?
引用鏈接
[1]
GitHub: https://github.com/flomesh-io/pipy
[2]
上一講功能: https://atbug.com/image-trusted-repository-with-open-policy-agent/
[3]
Admission Controller: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
關於雲原生社區
雲原生社區正式成立於 2020 年 5 月,是一箇中立的雲原生終端用戶社區,致力於爲開發者提供雲原生領域的專業資訊,推動中國雲原生產業發展,目標成爲中國雲原生領域最具影響力的開源社區。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Bo5RdEc7tTPjotJdqfApSQ