Thực thi các chính sách và quản trị đối với các Kubernetes workload
Trong bài viết này, Bizfly Cloud sẽ chia sẻ về việc thực thi các chính sách cho các Kubernetes workload bằng cách sử dụng các công cụ tĩnh như conftest và các operator trong cluster như Gatekeeper.
Các chính sách trong Kubernetes cho phép bạn ngăn chặn các workload cụ thể được triển khai trong cluster. Mặc dù tuân thủ thường là lý do để thực thi các chính sách nghiêm ngặt trong cluster, nhưng vẫn có một số phương pháp được đề xuất mà quản trị viên cluster nên thực hiện.
Ví dụ về các nguyên tắc như vậy là:
1. Không chạy các pod đặc quyền.
2. Không chạy pod với tư cách root user.
3. Không chỉ định giới hạn tài nguyên.
4. Không sử dụng thẻ mới nhất cho các container images.
5. Bây giờ cho phép các khả năng bổ sung của Linux theo mặc định.
Bên cạnh đó, bạn có thể muốn thực thi các chính sách riêng mà tất cả workload có thể muốn tuân theo, chẳng hạn như:
- Tất cả workload phải có nhãn "project" và app".
- Tất cả workload phải sử dụng các container image từ một nơi đăng ký container cụ thể (ví dụ: my-company.com).
Cuối cùng, có một loại kiểm tra thứ ba mà bạn muốn thực hiện dưới dạng chính sách để tránh gián đoạn dịch vụ của mình. Ví dụ về việc kiểm tra như vậy là đảm bảo rằng không có hai dịch vụ nào có thể sử dụng cùng một tên máy chủ truy cập.
Trong bài viết này, bạn sẽ tìm hiểu về việc thực thi các chính sách cho các Kubernetes workload của mình bằng cách sử dụng cả giải pháp ngoài cluster và trong cluster. Các chính sách này nhằm mục đích từ chối workload nếu chúng không đáp ứng thành công các điều kiện đã xác định.
Các phương pháp tiếp cận ngoài cluster được thực hiện bằng cách chạy kiểm thử tĩnh (static check) trên các YAML manifest trước khi chúng được gửi đến cluster.
Các phương pháp tiếp cận trong cluster sử dụng các Admission controller xác thực được gọi như một phần của yêu cầu API và trước khi tệp kê khai (manifest) được lưu trữ trong cơ sở dữ liệu.
Deployment không tuân thủ
Hãy xem xét YAML manifest dưới đây:
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-echo
labels:
app: http-echo
spec:
replicas: 2
selector:
matchLabels:
app: http-echo
template:
metadata:
labels:
app: http-echo
spec:
containers:
- name: http-echo
image: hashicorp/http-echo
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
- name: http-echo-1
image: hashicorp/http-echo:latest
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
Deployment ở trên sẽ tạo một pod bao gồm hai container từ cùng một container image. Container đầu tiên không chỉ định bất kỳ thẻ nào và container thứ hai chỉ định thẻ latest. Cả hai vùng chứa sẽ sử dụng phiên bản mới nhất của image hashicorp/http-echo,.
Đây được coi là một hành động không tốt và bạn muốn ngăn việc triển khai như vậy được tạo trong cluster của mình.
Cách tốt nhất là ghim container image vào thẻ chẳng hạn như hashicorp/http-echo:0.2.3.
Hãy xem cách bạn có thể phát hiện vi phạm chính sách bằng cách sử dụng kiểm thử tĩnh (static check).
Vì bạn muốn ngăn tài nguyên tiếp cận cluster, vị trí thích hợp để kiểm tra là:
- Là một GIT pre-commit, trước khi tài nguyên được commit với GIT.
- Là một phần của CI/CD pipeline của bạn trước khi nhánh được hợp nhất vào nhánh chính.
- Là một phần của CI/CD pipeline trước khi tài nguyên được gửi đến cluster.
Thực thi các chính sách bằng cách sử dụng conftest
Conftest là một hệ nhị phân và một framework kiểm thử cho dữ liệu cấu hình có thể được sử dụng để kiểm thử và xác minh các Kubernetes manifests. Các kiểm thử được viết bằng ngôn ngữ truy vấn được xây dựng có mục đích là Rego.
Hãy xác định 2 chính sách sau:
package main
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Bạn có thể đoán những gì hai chính sách đang kiểm tra?
Cả hai lần kiểm tra chỉ được áp dụng cho Deployment và được thiết kế để trích xuất tên image từ phần spec.container. Quy tắc cũ kiểm tra xem có thẻ được xác định trên image hay không.
package main
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Phần sau sẽ kiểm tra nếu một thẻ được xác định, nó không phải là thẻ mới nhất.
package main
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Lưu ý rằng, khi bạn có nhiều hơn một khối deny, conftest sẽ kiểm tra chúng một cách độc lập và kết quả tổng thể là bất kỳ khối nào vi phạm. Bây giờ, hãy lưu tệp dưới dạng check_image_tag.rego và chạy conftest đối với deployment.yaml manifests:
conftest test -p conftest-checks test-data/deployment.yaml
FAIL - test-data/deployment.yaml - image 'hashicorp/http-echo' doesn't specify a valid tag
FAIL - test-data/deployment.yaml - image 'hashicorp/http-echo:latest' uses latest tag
2 tests, 0 passed, 0 warnings, 2 failures
Nó đã phát hiện cả 2 vi phạm.
Vì conftest là một tệp nhị phân tĩnh, bạn có thể để nó chạy kiểm tra trước khi bạn gửi YAML đến cluster.
Nếu bạn đã sử dụng CI/CD pipeline để áp dụng các thay đổi cho cluster của mình, bạn có thể có thêm một bước xác thực tất cả các tài nguyên theo các chính sách conftest của mình.
Nhưng nó có thực sự ngăn ai đó gửi Deployment với thẻ latest không?
Tất nhiên, bất kỳ ai có đủ quyền vẫn có thể tạo workload trong cluster của bạn và bỏ qua CI/CD pipeline.
Nếu bạn có thể chạy kubectl apply -f deployment.yaml thành công, bạn có thể bỏ qua conftest, và cluster của bạn sẽ chạy các image có thẻ latest.
Làm cách nào bạn có thể ngăn người khác làm việc với các chính sách của bạn?
Bạn có thể bổ sung kiểm tra tĩnh (static check) bằng các chính sách động (dynamic policies) được triển khai bên trong cluster của bạn.
Điều gì sẽ xảy ra nếu bạn có thể từ chối một tài nguyên sau khi nó được gửi đến cluster?
Kubernetes API
Hãy tóm tắt lại những gì sẽ xảy ra khi bạn tạo một Pod như thế này trong cluster:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: sise
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
Bạn có thể triển khai Pod cho cluster với:
kubectl apply -f pod.yaml
Định nghĩa YAML được gửi đến máy chủ API và:
1. Định nghĩa YAML được lưu trữ trong etcd.
2. Bộ lập lịch chỉ định Pod cho một nút.
3. Kubelet lấy thông số Pod và tạo ra nó.
Nhưng điều gì đang xảy ra - nó có đơn giản như vậy không? Điều gì xảy ra khi có lỗi đánh máy trong YAML? Ai ngăn bạn gửi các tài nguyên bị hỏng cho etcd?
Khi bạn nhập kubectl apply một vài điều sẽ xảy ra.
Hệ nhị phân kubectl:
1. Xác thực tài nguyên phía client (có lỗi rõ ràng nào không?).
2. Chuyển đổi YAML payload thành JSON.
3. Đọc cấu hình từ của bạn KUBECONFIG.
4. Gửi yêu cầu payload tới kube-apiserver.
Khi kube-apiserver nhận được yêu cầu, nó không lưu trữ nó trong etcd ngay lập tức. Đầu tiên, nó phải xác thực yêu cầu.
Bạn có quyền truy cập vào cluster không có nghĩa là bạn có thể tạo hoặc đọc tất cả các tài nguyên. Việc cấp quyền thường được thực hiện với Role-Based Access Control (RBAC). Với RBAC, bạn có thể chỉ định các quyền chi tiết và hạn chế những gì người dùng hoặc ứng dụng có thể làm. Tại thời điểm này, bạn đã được kube-apiserver xác thực và ủy quyền.
Các kube-apiserver được thiết kế như một đường ống dẫn (pipeline). Yêu cầu đi qua một loạt các thành phần trước khi nó được lưu trữ trong cơ sở dữ liệu. Trong khi ủy quyền và xác thực là hai thành phần đầu tiên, chúng không phải là những thành phần duy nhất.
Trước khi đối tượng đến cơ sở dữ liệu, nó sẽ bị chặn bởi Admission controllers..
Ở giai đoạn này có cơ hội để thực hiện kiểm tra thêm đối với tài nguyên hiện tại. Và Kubernetes có rất nhiều Admission controllers được bật theo mặc định.
Bạn có thể kiểm tra tất cả các Admission controllers được bật trong minikube với kubectl -n kube-system describe pod kube-apiserver-minikube. Đầu ra phải chứa flag --enable-admission-plugins và danh sách các Admission controller.
Ví dụ, chúng ta hãy xem xét các Admission controller NamespaceLifecycle.
Xác thực Admission controllers
Admission controllers (Kiểm soát thu nhận) NamespaceLifecycle ngăn bạn tạo các Pod trong các namespace chưa tồn tại. Bạn có thể xác định một Pod với một namespace như sau:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
namespace: does-not-exist
spec:
containers:
- name: sise
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
Định nghĩa YAML là hợp lệ, do đó kiểm tra xác thực kubectl được thông qua và yêu cầu được gửi đến cluster:
kubectl apply -f pod-namespaced.yaml
Giả sử rằng bạn đã được xác thực và ủy quyền, yêu cầu sẽ đến được với Admission controllers NamespaceLifecycle và được kiểm tra.
Namespace does-not-exist không tồn tại và cuối cùng bị từ chối.
Ngoài ra, các Admission controllers NamespaceLifecycle dừng yêu cầu mà có thể xóa default, kube-system và namespace kube-public.
Controller kiểm tra hành động và tài nguyên được nhóm trong danh mục Validating. Có một loại Controller khác được gọi là Mutating .
Mutating admission controllers
Admission controller DefaultStorageClass là một ví dụ về mutating controller.
Giả sử rằng bạn muốn tạo Persistent Volume Claim (PVC) như sau:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
Bạn có thể tạo Persistent Volume Claim (PVC) bằng:
kubectl apply -f pvc.yaml
Nếu bạn kiểm tra danh sách Persistent Volume Claim (PVC) với kubectl get pvc, bạn có thể nhận thấy rằng volume là Bound và storage class là standard.
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound pvc-059f2da2 3Gi RWO standard 3s
Tuy nhiên, bạn đã không chỉ định bất kỳ StorageClass "standard" nào trong YAML.
Bạn có thể kiểm tra định nghĩa YAML cho PVC với:
kubectl get pvc my-pvc -0 yaml
Nếu bạn chú ý đến định nghĩa, bạn có thể nhận thấy rằng có một số trường bổ sung được thêm vào:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
storageClassName: standard
volumeMode: Filesystem
volumeName: pvc-059f2da2-a216-42b7-875e-e7da327605dd
Tên "standard" không được mã hóa cứng trong API. Thay vào đó, tên của StorageClass mặc định được đưa vào spec.storageClassName.
Bạn có thể truy xuất tên StorageClass default từ cụm với:
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE AGE
standard (default) k8s.io/minikube-hostpath Delete Immediate 8m
Nếu tên của StorageClass default là "aws-ebs", DefaultStorageClass Admission controller (Kiểm soát thu nhận) sẽ chèn nó thay vì "standard".
Khi yêu cầu đi qua Admission controller (Kiểm soát thu nhận), cuối cùng nó được lưu trữ trong etcd.
Admission controllers (Kiểm soát thu nhận ) được thiết kế để thúc đẩy khả năng mở rộng.
Bạn có thể sử dụng Admission controller (Kiểm soát thu nhận) mặc định đi kèm với Kubernetes hoặc bạn có thể cắm bộ điều khiển của riêng mình.
Hai Admission controller (Kiểm soát thu nhận) có thể lập trình:
- các MutationAdmissionWebhook, và
- NS ValidationAdmissionWebhook
Bạn có thể đăng ký một thành phần vào Mutation hoặc Validation webhook, và những controller đó sẽ gọi nó khi yêu cầu chuyển qua giai đoạn Admission.
Vì vậy, bạn có thể viết một thành phần kiểm tra xem Pod hiện tại có sử dụng container image đến từ đăng ký riêng tư hay không.
Bạn có thể đăng ký nó như một phần của ValidationAdmissionWebhook và thông qua hoặc từ chối các yêu cầu dựa trên container image.
Và đó chính xác là những gì Gatekeeper làm - nó đăng ký như một thành phần trong cluster và xác nhận các yêu cầu.
Thực thi các chính sách bằng Gatekeeper
Gatekeeper cho phép quản trị viên Kubernetes triển khai các chính sách để đảm bảo tuân thủ và các phương pháp hay nhất trong cluster. Gatekeeper tự đăng ký làm controller với webhook xác thực trong API Kubernetes.
Bất kỳ tài nguyên nào được gửi đến cluster đều bị chặn và kiểm tra dựa trên danh sách các chính sách đang hoạt động.
Ngoài ra, Gatekeeper bao gồm các native concept của Kubernetes như Custom Resource Definitions (CRD) và do đó các chính sách được quản lý dưới dạng tài nguyên Kubernetes.
Từ bên trong, Gatekeeper sử dụng Open Policy Agent (OPA) để triển khai công cụ chính sách cốt lõi và các chính sách được viết bằng ngôn ngữ Rego - cùng một ngôn ngữ được sử dụng bởi conftest.
Trong phần tiếp theo, bạn sẽ dùng thử Gatekeeper.
Bạn sẽ cần quyền truy cập vào một Kubernetes cluster với các đặc quyền cấp quản trị viên, chẳng hạn như đặc quyền mà bạn có thể thiết lập bằng cách sử dụng minikube.
Khi bạn đã có cấu hình kubectl để giao tiếp với cluster, hãy chạy lệnh dưới đây để thiết lập gatekeeper:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
Để xác minh xem gatekeeper có được thiết lập chính xác hay không, hãy chạy lệnh:
kubectl -n gatekeeper-system describe svc gatekeeper-webhook-service
Name: gatekeeper-webhook-service
Namespace: gatekeeper-system
Labels: gatekeeper.sh/system=yes
Annotations: ...
Type: ClusterIP
IP: 10.102.199.165
Port: <unset> 443/TCP
TargetPort: 8443/TCP
Endpoints: 172.18.0.4:8443
# more output ...
Đây là dịch vụ được gọi bởi Kubernetes API như là một phần của quá trình xử lý yêu cầu trong giai đoạn "Validating Admission".
Tất cả các Pod, Deployments, Services,, v.v. hiện đã được Gatekeeper chặn và xem xét kỹ lưỡng.
Xác định các chính sách có thể sử dụng lại bằng ConstraintTemplate
Trong Gatekeeper, trước tiên bạn cần tạo một chính sách bằng cách sử dụng một tài nguyên tùy chỉnh ConstraintTemplate.
Hãy xem một ví dụ.
Định nghĩa ConstraintTemplate dưới đây từ chối bất kỳ triển khai nào sử dụng thẻ mới nhất:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8simagetagvalid
spec:
crd:
spec:
names:
kind: K8sImageTagValid
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simagetagvalid
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Chính sách này tương tự như chính sách trước đó mà bạn đã sử dụng conftest.
Hãy xem:
package main
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
deny[msg] {
input.kind == "Deployment"
image := input.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Đối tượng đầu vào có sẵn như input.review.object thay thế input và không cần xác nhận loại đối tượng đầu vào ở đây.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8simagetagvalid
spec:
crd:
spec:
names:
kind: K8sImageTagValid
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simagetagvalid
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Quy tắc từ chối được đổi tên thành vi phạm.
Trong conftest, chữ ký quy tắc là Deny[msg] {...} trong khi ở Gatekeeper là violation[{"msg": msg}] {...}.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8simagetagvalid
spec:
crd:
spec:
names:
kind: K8sImageTagValid
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simagetagvalid
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Khối quy tắc vi phạm có một chữ ký cụ thể - một đối tượng có hai thuộc tính. Đầu tiên là thuộc tính chuỗi msg. Cái sau là đối tượng details chứa các thuộc tính tùy ý và bạn có thể thêm với bất kỳ giá trị nào. Trong trường hợp này, nó là một đối tượng rỗng. Cả hai đều được sử dụng làm giá trị trả về:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8simagetagvalid
spec:
crd:
spec:
names:
kind: K8sImageTagValid
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simagetagvalid
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
not count(split(image, ":")) == 2
msg := sprintf("image '%v' doesn't specify a valid tag", [image])
}
violation[{"msg": msg, "details":{}}] {
image := input.review.object.spec.template.spec.containers[_].image
endswith(image, "latest")
msg := sprintf("image '%v' uses latest tag", [image])
}
Bây giờ, hãy tạo ConstraintTemplate:
kubectl apply -f templates/check_image_tag.yaml
constrainttemplate.templates.gatekeeper.sh/k8simagetagvalid created
Bạn có thể chạy kubectl describe để truy vấn mẫu từ cluster:
kubectl describe constrainttemplate.templates.gatekeeper.sh/k8simagetagvalid
Name: k8simagetagvalid
Namespace:
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"templates.gatekeeper.sh/v1beta1","kind":"ConstraintTemplate","metadata":
{"annotations":{},"name":"k8simagetagvalid"},"spec"...
API Version: templates.gatekeeper.sh/v1beta1
Kind: ConstraintTemplate
# more output ...
Tuy nhiên, ConstraintTemplate không phải là thứ bạn có thể sử dụng để xác thực các deployment. Đó chỉ là một định nghĩa về một chính sách chỉ có thể được thực thi bằng cách tạo một Constraint.
Tạo một constraint
Constraint là một cách để nói "Tôi muốn áp dụng chính sách này cho cluster" .
Các ràng buộc là một ví dụ cụ thể của một công thức ConstraintTemplate.
Hãy xem một ví dụ.
Phần sau Constraint sử dụng ConstraintTemplate (công thức) đã xác định trước đó K8sImageTagValid:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageTagValid
metadata:
name: valid-image-tag
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
Chú ý cách các Constraint tham chiếu ConstraintTemplate cũng như loại tài nguyên mà nó nên được áp dụng. Đối tượng spec.match xác định workload mà constraint sẽ được thực thi. Tại đây, bạn chỉ định rằng nó sẽ được thực thi đối với nhóm API apps và thuộc loại Deployment.
Vì các trường này là mảng, bạn có thể chỉ định nhiều giá trị và mở rộng kiểm tra cho StatefulSets, DaemonSets, v.v. Lưu nội dung trên vào một tệp mới và đặt tên check_image_tag_constraint.yaml.
Chạy kubectl apply để tạo constraint :
kubectl apply -f check_image_tag_constraint.yaml
k8simagetagvalid.constraints.gatekeeper.sh/valid-image-tag created
Sử dụng kubectl describe để đảm bảo rằng constraint đã được tạo:
kubectl describe k8simagetagvalid.constraints.gatekeeper.sh/valid-image-tag
Name: valid-image-tag
Namespace:
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"constraints.gatekeeper.sh/v1beta1","kind":"K8sImageTagValid","metadata":
{"annotations":{},"name":"valid-image-tag"},"spec":...
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sImageTagValid
Metadata:
Creation Timestamp: 2020-07-01T07:57:23Z
# more output...
Kiểm thử chính sách
Bây giờ, hãy kiểm thử Deployment với hai container image:
kubectl apply -f deployment.yaml
Error from server ([denied by valid-image-tag] image 'hashicorp/http-echo' doesn't specify a valid tag
[denied by valid-image-tag] image 'hashicorp/http-echo:latest' uses latest tag): error when creating
"test-data/deployment.yaml": admission webhook "validation.gatekeeper.sh" denied the request:
[denied by valid-image-tag] image 'hashicorp/http-echo' doesn't specify a valid tag
[denied by valid-image-tag] image 'hashicorp/http-echo:latest' uses latest tag
Việc triển khai bị từ chối bởi chính sách Gatekeeper.
Lưu ý cách kiểm tra được tích hợp trong API Kubernetes.
Bạn không thể bỏ qua kiểm tra hoặc làm việc xung quanh nó.
Việc triển khai các chính sách Gatekeeper trong một cluster với workload tồn tại có thể là một thách thức vì bạn không muốn làm gián đoạn việc triển khai các workload quan trọng do không tuân thủ. Do đó, Gatekeeper cho phép thiết lập các ràng buộc trong chế độ dry-run bằng cách chỉ định enforcementAction: dryrun trong thông số kỹ thuật:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageTagValid
metadata:
name: valid-image-tag
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
Trong chế độ này, chính sách không ngăn bất kỳ workload nào được triển khai nhưng nó ghi lại bất kỳ vi phạm nào.
Các vi phạm sẽ được ghi vào trường Violations của lệnh kubectl describe:
kubectl describe k8simagetagvalid.constraints.gatekeeper.sh/valid-image-tag
Name: valid-image-tag
Namespace:
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
....
Total Violations: 2
Violations:
Enforcement Action: dryrun
Kind: Deployment
Message: image 'hashicorp/http-echo' doesn't specify a valid tag
Name: http-echo
Namespace: default
Enforcement Action: dryrun
Kind: Deployment
Message: image 'hashicorp/http-echo:latest' uses latest tag
Name: http-echo
Namespace: default
Events: <none>
Khi bạn biết rằng tất cả các bản kê khai của mình đều tuân thủ, bạn có thể loại bỏ chế độ dry-run và chủ động ngăn chặn vi phạm.
Hãy xem một ví dụ khác.
Bạn có thể viết một chính sách buộc Deployment có hai nhãn: project cho tên dự án hiện tại và app cho tên ứng dụng không?
Chính sách để thực thi các nhãn nhất quán
Trước tiên, bạn nên thực thi chính sách này như một sự kiểm tra cho conftest:
package main
deny[msg] {
input.kind == "Deployment"
required := {"app", "project"}
provided := {label | input.metadata.labels[label]}
missing := required - provided
count(missing) > 0
msg = sprintf("you must provide labels: %v", [missing])
}
Hãy xem chính sách chi tiết:
Required là một tập hợp có hai thành viên - app và project.
Các nhãn đó phải được hiển thị trên tất cả các Deployment.
package main
deny[msg] {
input.kind == "Deployment"
required := {"app", "project"}
provided := {label | input.metadata.labels[label]}
missing := required - provided
count(missing) > 0
msg = sprintf("you must provide labels: %v", [missing])
}
provided truy xuất tập hợp các nhãn được chỉ định trong đầu vào.
package main
deny[msg] {
input.kind == "Deployment"
required := {"app", "project"}
provided := {label | input.metadata.labels[label]}
missing := required - provided
count(missing) > 0
msg = sprintf("you must provide labels: %v", [missing])
}
Sau đó, một phép toán khác biệt tập hợp được thực hiện và một tập hợp mới có chứa các nhãn có trong đó required, nhưng không được tìm thấy trong provided được tạo ra.
package main
deny[msg] {
input.kind == "Deployment"
required := {"app", "project"}
provided := {label | input.metadata.labels[label]}
missing := required - provided
count(missing) > 0
msg = sprintf("you must provide labels: %v", [missing])
}
Nếu số phần tử trong tập hợp này lớn hơn 0, quy tắc bị vi phạm.
Điều này đạt được bằng cách sử dụng hàm count() để kiểm tra số lượng phần tử trong tập hợp missing.
Chạy thử conftest chỉ định chính sách này và bạn sẽ thấy lỗi:
conftest test -p conftest-checks/check_labels.rego test-data/deployment.yaml
FAIL - test-data/deployment.yaml - you must provide labels: {"project"}
1 test, 0 passed, 0 warnings, 1 failure
Bạn có thể khắc phục sự cố bằng cách thêm nhãn project và app, như sau:
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-echo
labels:
app: http-echo
project: test
spec:
replicas: 2
selector:
matchLabels:
app: http-echo
template:
metadata:
labels:
app: http-echo
spec:
containers:
- name: http-echo
image: hashicorp/http-echo
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
- name: http-echo-1
image: hashicorp/http-echo:latest
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
Điều gì xảy ra khi ai đó triển khai tài nguyên trực tiếp vào cụm?
Họ có thể làm việc xung quanh chính sách.
Vì vậy, hãy thêm chính sách tương tự trong Gatekeeper.
Đầu tiên, bạn sẽ phải tạo ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
labels:
type: array
items: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Tệp manifest trên minh họa cách bạn có thể chỉ định các tham số đầu vào cho mẫu constraint để định cấu hình hành vi của nó trong runtime.
Quay trở lại ConstraintTemplate với tư cách là một công thức tương tự, các thông số đầu vào là một cách mà công thức cho phép bạn tùy chỉnh các thành phần nhất định.
Tuy nhiên, các thành phần này phải tuân theo các quy tắc cụ thể.
Ví dụ: bạn chỉ muốn cho phép một mảng chuỗi làm đầu vào cho ConstraintTemplate.
Bạn có thể mô tả đầu vào bằng lược đồ OpenAPIV3 trong đối tượng validation trong ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
labels:
type: array
items: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Lược đồ này xác định rằng mẫu constraint chỉ mong đợi có một tham số, labels đó là một mảng các chuỗi. Tất cả đầu vào được cung cấp đều có sẵn cho constraint thông qua thuộc tính input.parameters. Lưu tệp kê khai ở trên vào một tệp check_labels.yaml và sau đó chạy kubectl apply để tạo mẫu constraint:
kubectl apply -f check_labels.yaml
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created
Để sử dụng chính sách từ ConstraintTemplate, bạn cần có Constraint.
Tạo một tệp mới, check_labels_constraints.yaml với nội dung sau:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: deployment-must-have-labels
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
labels: ["app", "project"]
Chạy kubectl apply để tạo constraint:
kubectl apply -f check_labels_constraints.yaml
k8srequiredlabels.constraints.gatekeeper.sh/deployment-must-have-labels created
Ở giai đoạn này, bạn có hai constraint được tạo trong cụm của mình:
- Constraint đầu tiên kiểm tra việc triển khai của bạn có đang sử dụng image có thẻ hợp lệ hay không.
- Constraint thứ hai kiểm tra việc triển khai của bạn có xác định hai nhãn app và project.
Bây giờ, hãy thử tạo triển khai được mô tả ở đầu bài viết:
kubectl apply -f deployment.yaml
Error from server ([denied by deployment-must-have-labels] you must provide labels: {"project"}
[denied by valid-image-tag] image 'hashicorp/http-echo' doesn't specify a valid tag
[denied by valid-image-tag] image 'hashicorp/http-echo:latest' uses latest tag): error when creating
"deployment.yaml": admission webhook "validation.gatekeeper.sh" denied the request:
[denied by deployment-must-have-labels] you must provide labels: {"project"}
[denied by valid-image-tag] image 'hashicorp/http-echo' doesn't specify a valid tag
[denied by valid-image-tag] image 'hashicorp/http-echo:latest' uses latest tag
Việc triển khai không được tạo thành công vì nó không có nhãn project bắt buộc cũng như không sử dụng image tag hợp lệ.
Công việc hoàn thành!
Tệp kê khai triển khai hợp lệ sẽ được triển khai thành công sẽ có hai nhãn (app và project) cũng như sử dụng image được gắn thẻ:
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-echo
labels:
app: http-echo
project: test
spec:
replicas: 2
selector:
matchLabels:
app: http-echo
template:
metadata:
labels:
app: http-echo
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:0.2.3
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
- name: http-echo-1
image: hashicorp/http-echo:0.2.1
args: ["-text", "hello-world"]
ports:
- containerPort: 5678
Bạn đã xác thực Deployment của mình bằng kiểm tra động và tĩnh.
Tóm lược
Cả conftest và Gatekeeper đều sử dụng ngôn ngữ Rego để xác định các chính sách, điều này làm cho hai công cụ này trở thành một giải pháp hấp dẫn để thực hiện kiểm tra ngoài cụm và kiểm tra trong cụm tương ứng. Tuy nhiên, như bạn đã thấy ở trên, cần thực hiện một số thay đổi để chính sách Rego hợp nhất hoạt động với Gatekeeper.
Các konstraint project có mục tiêu giúp đỡ trong vấn đề này. Tiền đề của konstraint một chính sách mà bạn sẽ viết trong Rego conftest và sau đó tạo ConstraintTemplate và tài nguyên Constraint cho Gatekeeper.
Konstraint tự động hóa các bước thủ công liên quan đến việc chuyển đổi một chính sách được viết cho conftest thành một chính sách hoạt động trong Gatekeeper.
Bên cạnh đó, việc thử nghiệm các mẫu Gatekeeper constraint và constraint cũng được thực hiện dễ dàng hơn bằng cách sử dụng konstraint.
Cả conftest và gatekeeper đều không phải là giải pháp duy nhất khi nói đến việc thực thi các chính sách ngoài cluster và trong cluster tương ứng.
Điều làm cho hai giải pháp trở nên hấp dẫn là bạn có thể sử dụng Rego để thực hiện các chính sách cho cả hai công cụ.
Bạn thậm chí có thể tiến xa hơn khi triển khai một tập hợp con các chính sách có liên quan cả bên trong và bên ngoài cluster. Một giải pháp so sánh đạt được mức độ thực thi chính sách như nhau là polaris có cả chức năng thực thi chính sách ngoài cluster và trong cluster. Tuy nhiên, nó sử dụng ngôn ngữ đặc tả chính sách dựa trên lược đồ JSON tùy chỉnh và do đó có thể không biểu đạt như Rego.
Bizfly Kubernetes Engine với những tính năng ưu việt và vượt trội sẽ mang lại những tác động tích cực đến các Dev team/DevOps cũng như toàn bộ doanh nghiệp. Nếu bạn đang muốn bắt đầu với Kubernetes, truy cập: https://bizflycloud.vn/kubernetes-engine để trải nghiệm miễn phí ngay hôm nay.
>> Có thể bạn quan tâm: Cách triển khai Nginx Ingress với Cert-Manager trên Bizfly Kubernetes Engine