Styra DASを使ってOPAをより簡単に
はじめに
今回は、Qiita: Kubernetes Advent Calendar 2020 その3 23日目の記事です。
今回の題材は、KubeCon NA 2020においてStyraが発表したStyra DAS(Declarative Authorisation Service)のフリーエディションです!
実際に使ってみて、今まで手が出しにくかったOPAが、とても楽に管理できたのでこちらで紹介させていただければと思います。
Styra DAS Free
OPA Installation
まずこちらからアクセスして、登録を行ってください。
登録して少し待つと、以下の画面に行けるようになります。
ここからQuick Startに沿って、Kubernetes Clusterを登録していきます。
まずここで、名前を設定しAdd system
をクリックします。
※ Read-onlyは外しておきましょう。
その後、Quick Startの2. Install Styra OPA on your cluster
をクリックします。
次に、各コマンドを入力して、OPAをインストールします。
コマンドで起動を確認します。
% kubectl get all -n styra-system NAME READY STATUS RESTARTS AGE pod/datasources-agent-6f96f7d4c9-8z79l 1/1 Running 0 2m34s pod/opa-7979b56c84-8bdbk 2/2 Running 0 2m34s pod/opa-7979b56c84-d6d87 2/2 Running 0 2m34s pod/opa-7979b56c84-sbmcd 2/2 Running 0 2m34s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/opa ClusterIP 10.103.17.84 <none> 443/TCP 2m34s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/datasources-agent 1/1 1 1 2m34s deployment.apps/opa 3/3 3 3 2m34s NAME DESIRED CURRENT READY AGE replicaset.apps/datasources-agent-6f96f7d4c9 1 1 1 2m34s replicaset.apps/opa-7979b56c84 3 3 3 2m34s
ここで、Quick Startを抜けていきます。
左上のロゴの近くの?マークから、Quick Startを再開することができます。
Validation
ここから、Validationを見ていきたいと思います。
Validationとは、受け取ったリクエストを通すか弾くかを決めるものです。
[Cluster name] > [Validating] > [Rules]に移動します。
Add rules
を選択します。
ここでは、prebuiltのruleが入っています。
今回は、
を設定していきます。
右上のValidate
を選択することで、既存のClusterにおいて今回適用するruleに対して違反しているリソースがないかを確認することができます。
各ruleにおいて、現在Monitor
となっているところをEnforce
に変更します。
下記コマンドが、現状通ることを確認します。
% kubectl run nginx --image=nginx:latest pod/nginx created % kubectl apply -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress ingress.networking.k8s.io/example-ingress created
確認ができましたので、下記コマンドにてリソースを削除します。
% kubectl delete pod nginx pod "nginx" deleted % kubectl delete -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress ingress.networking.k8s.io "example-ingress" deleted
それでは、Publishを押します。
確認が表示されるので、Publish changes
を選択します。
それでは、それぞれのruleが設定されたかを確認します。
% kubectl run nginx --image=nginx:latest Error from server: admission webhook "validating-webhook.openpolicyagent.org" denied the request: Enforced: Resource Pod/default/nginx should not use the 'latest' tag on container image nginx:latest. % kubectl apply -f https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress Error from server: error when creating "https://raw.githubusercontent.com/StyraInc/configuration-examples/main/kubernetes/ingresses/non-tls-ingress.yaml": admission webhook "validating-webhook.openpolicyagent.org" denied the request: Enforced: Resource Ingress/default/example-ingress should have TLS enabled.
Errorになることが確認できました!
Mutation
Mutationとは、リクエストにに対して、パラメータを追加したり変更したりすることができるものです。
DAS にアクセスし、[Cluster name] > [Mutating] > [Rules]に移動します。
Add rules
を選択します。
ここではValidationと同様に、prebuiltのruleが入っています。
今回は、
- Containers: Add Default Memory Limit
- Containers: Add Default CPU Limit
を追加していきます。
値は、環境に合わせて設定してください。
まず、適用していない場合の状況を確認します。
% kubectl run nginx --image=nginx:1.19.6 pod/nginx created % kubectl describe pods nginx Name: nginx Namespace: default Priority: 0 Node: minikube/192.168.49.2 Start Time: Sat, 19 Dec 2020 20:51:24 +0900 Labels: run=nginx Annotations: <none> Status: Running IP: 172.17.0.7 IPs: IP: 172.17.0.7 Containers: nginx: Container ID: docker://9cfd38b13211ab997d362f59c6849662b50e844c05c6af4392978f167d5234c1 Image: nginx:1.19.6 Image ID: docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9 Port: <none> Host Port: <none> State: Running Started: Sat, 19 Dec 2020 20:51:29 +0900 Ready: True Restart Count: 0 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-85jn9 (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-85jn9: Type: Secret (a volume populated by a Secret) SecretName: default-token-85jn9 Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 93s default-scheduler Successfully assigned default/nginx to minikube Normal Pulling 92s kubelet Pulling image "nginx:1.19.6" Normal Pulled 89s kubelet Successfully pulled image "nginx:1.19.6" in 3.5937294s Normal Created 88s kubelet Created container nginx Normal Started 88s kubelet Started container nginx
リソースを消していきます。
% kubectl delete pod nginx pod "nginx" deleted
Enforce
に移動させ、Publish
をクリックして、ruleを適用します。
再度、同じリソースを作成し確認していきます。
% kubectl run nginx --image=nginx:1.19.6 pod/nginx created % kubectl describe pods nginx Name: nginx Namespace: default Priority: 0 Node: minikube/192.168.49.2 Start Time: Sat, 19 Dec 2020 20:58:47 +0900 Labels: run=nginx Annotations: <none> Status: Running IP: 172.17.0.7 IPs: IP: 172.17.0.7 Containers: nginx: Container ID: docker://86d42940ef4206bdee92c88919f4560b8e7f8ccecc105d2e333eb7dcf375114a Image: nginx:1.19.6 Image ID: docker-pullable://nginx@sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9 Port: <none> Host Port: <none> State: Running Started: Sat, 19 Dec 2020 20:58:48 +0900 Ready: True Restart Count: 0 Limits: cpu: 2 memory: 100Mi Requests: cpu: 2 memory: 100Mi Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-85jn9 (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-85jn9: Type: Secret (a volume populated by a Secret) SecretName: default-token-85jn9 Optional: false QoS Class: Guaranteed Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 38s default-scheduler Successfully assigned default/nginx to minikube Normal Pulled 37s kubelet Container image "nginx:1.19.6" already present on machine Normal Created 37s kubelet Created container nginx Normal Started 37s kubelet Started container nginx
適用されていることが確認できました!
終わりに
ここまで、簡単にOPAを導入していく方法を見ていきました。
prebuiltのruleを用いることで、とても簡単に制御できることがわかったと思います。
ここまで読んでいただき、ありがとうございました!
引き続き、Cloud Nativeを盛り上げていきましょう!
Appendixとして、自分でregoを書いていく際に役に立つ機能について書かせていただきますので、ぜひこちらも読んでいただけると嬉しいです。
Appendix
Admission Review
これまで、prebuiltのruleを用いてPolicyを作成してきましたが、 実運用をしていく中で、自分でregoを書いてruleを作っていく必要が出てきます。
OPAにおいて、Regoを用いてruleを記述する対象となるのがAdmission Review
になります。
regoとAdmissoin Reviewの関係は、OPA Playgroundで、簡単に試すことができます。
ただ、あらゆるリソースに対してこのAddmission Reviewを追うのは大変です。
Styra DAS Freeでは、このAddmission ReviewをLogとして記録してくれています。
[Cluster name]を選択し、Decisions
タブを選択することで確認することができます。
今後ruleを適用しようと考えているリソースを作成します。
% kubectl create deployment nginx-deploy --image=nginx:1.19.6 deployment.apps/nginx-deploy created
下のFilter
にて、作成したリソース名を入力します。
それでは、実際に作成したDeploymentに対してValidatingを行なっている箇所を選択します。
Inputのところを抜き出します。
Admission Review
"input": {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"dryRun": false,
"kind": {
"group": "apps",
"kind": "Deployment",
"version": "v1"
},
"name": "nginx-deploy",
"namespace": "default",
"object": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"creationTimestamp": "2020-12-19T12:10:57Z",
"generation": 1,
"labels": {
"app": "nginx-deploy"
},
"managedFields": [
{
"apiVersion": "apps/v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:labels": {
".": {},
"f:app": {}
}
},
"f:spec": {
"f:progressDeadlineSeconds": {},
"f:replicas": {},
"f:revisionHistoryLimit": {},
"f:selector": {
"f:matchLabels": {
".": {},
"f:app": {}
}
},
"f:strategy": {
"f:rollingUpdate": {
".": {},
"f:maxSurge": {},
"f:maxUnavailable": {}
},
"f:type": {}
},
"f:template": {
"f:metadata": {
"f:labels": {
".": {},
"f:app": {}
}
},
"f:spec": {
"f:containers": {
"k:{\"name\":\"nginx\"}": {
".": {},
"f:image": {},
"f:imagePullPolicy": {},
"f:name": {},
"f:resources": {},
"f:terminationMessagePath": {},
"f:terminationMessagePolicy": {}
}
},
"f:dnsPolicy": {},
"f:restartPolicy": {},
"f:schedulerName": {},
"f:securityContext": {},
"f:terminationGracePeriodSeconds": {}
}
}
}
},
"manager": "kubectl-create",
"operation": "Update",
"time": "2020-12-19T12:10:57Z"
}
],
"name": "nginx-deploy",
"namespace": "default",
"uid": "8d27da1b-c9cb-4453-ba5e-d79f051fdb48"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "nginx-deploy"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "nginx-deploy"
}
},
"spec": {
"containers": [
{
"image": "nginx:1.19.6",
"imagePullPolicy": "IfNotPresent",
"name": "nginx",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30
}
}
},
"status": {}
},
"oldObject": null,
"operation": "CREATE",
"options": {
"apiVersion": "meta.k8s.io/v1",
"fieldManager": "kubectl-create",
"kind": "CreateOptions"
},
"requestKind": {
"group": "apps",
"kind": "Deployment",
"version": "v1"
},
"requestResource": {
"group": "apps",
"resource": "deployments",
"version": "v1"
},
"resource": {
"group": "apps",
"resource": "deployments",
"version": "v1"
},
"uid": "1040a718-e0c6-47ec-93c2-514d55c41d37",
"userInfo": {
"groups": [
"system:masters",
"system:authenticated"
],
"username": "minikube-user"
}
}
},
これが、Inputとして入ってきたAddmission Reviewになります。 OPAはこれに対してruleを用いて、ValidationやMutationを行なっていることが分かります。
Writing Mutating Rule
では、上のリソースに対して実際にruleを書いていきます。
今回はこのようなruleを書いてみました。
Mutating Rule
enforce[decision] {
#title: Restrict Replica
input.request.object.kind == "Deployment"
# Check if the replica count is more than 2
input.request.object.spec.replicas > 2
# As all of the above conditions are true, the request will be updated with
# the following JSON patch and respective decision will be returned.
decision := {
"allowed": true,
"message": "replacing replicas",
"patch": [
{
"op": "replace",
"path": "/spec/replicas",
"value": 2
}
]
}
}
内容としては、replica数が2より大きい場合に、replica数を2に書き換えるという内容になっています。
では、コマンドで確認していきたいと思います。
% kubectl create deployment nginx-deploy --image=nginx:1.19.6 --replicas=5 deployment.apps/nginx-deploy created % kubectl get pods NAME READY STATUS RESTARTS AGE nginx-deploy-5dbf8f84ff-29lbm 1/1 Running 0 7s nginx-deploy-5dbf8f84ff-rh54m 1/1 Running 0 7s
replicaを5にしているにもかかわらず、ちゃんとreplicaが2として生成されました。
DASにおいても、Advice
という項目でMutationが実施されたことを確認できます。
まだ、regoを完全に理解できているわけではないので、分かっていない部分ではありますが、
一般的なOPAを使う時とDASを通して使う時で、regoの文法等は一緒なのですが、表現方法が若干違うように感じました。
この辺り、もっと突き詰めていきたいと思います。