Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Belgium Kubernetes Meetup: Intro to CRDs and Controllers

Belgium Kubernetes Meetup: Intro to CRDs and Controllers

CustomResourceDefinitions (CRDs) are used to extend a Kubernetes cluster with 3rd-party API resources, without changing the source code of Kubernetes. This opens up Kubernetes to be used as a platform for 3rd-party products or inhouse solutions.

Dr. Stefan Schimanski

June 15, 2018
Tweet

More Decks by Dr. Stefan Schimanski

Other Decks in Programming

Transcript

  1. Intro to CustomResources & Controllers Stefan Schimanski [email protected] / sttts

    @ GitHub @the_sttts Kubernetes Meetup Belgium – June 15 Hosted by
  2. Restful http API / /version /api /api/v1/pods /api/v1/pods/<name> /api/v1/pods/<name>/status /apis

    /apis/batch /apis/batch/v2alpha1 /apis/batch/v2alpha1/jobs /apis/batch/v2alpha1/cronjobs /apis/batch/v1 /apis/batch/v1/jobs kube-apiserver kubelet proxy Node: $ kubectl create -f foo.yaml User: scheduler controller manager apiserver ingress controller Master: cloud native apps Pods:
  3. Restful http API / /version /api /api/v1/pods /api/v1/pods/<name> /api/v1/pods/<name>/status /apis

    /apis/batch /apis/batch/v2alpha1 /apis/batch/v2alpha1/jobs /apis/batch/v2alpha1/cronjobs /apis/batch/v1 /apis/batch/v1/jobs /apis/<our-group>/v1/<your-resource> kube-apiserver kubelet proxy Node: $ kubectl create -f foo.yaml User: scheduler controller manager apiserver ingress controller Master: cloud native apps Pods:
  4. $ kube-apiserver --secure-port 0 --etcd-servers http://127.0.0.1:2379 --service-cluster-ip-range 10.0.0.0/16 --storage-backend etcd2

    --storage-media-type application/json $ etcd $ kubectl config set-cluster local --server=http://127.0.0.1:8080 $ kubectl config set-context local --cluster=local $ kubectl config use-context local $ kubectl get namespaces --v=7 $ kubectl get namespace default -o json $ kubectl annotate namespace default meetup=hello $ curl http://127.0.0.1:8080/api/v1/namespaces/default $ etcdctl ls --recursive $ etcdctl get /registry/namespaces/default $ etcdctl -o extended get /registry/namespaces/default
  5. $ http GET http://127.0.0.1:8080/api/v1/namespaces/default { "apiVersion": "v1", "kind": "Namespace", "metadata":

    { "annotations": { "meetup": “hallo" }, "creationTimestamp": "2017-03-10T07:51:39Z", "name": "default", "resourceVersion": “73", }, "spec": { "finalizers": ["kubernetes“] }, "status": { "phase": "Active“ } } $ http GET http://127.0.0.1:8080/api/v1/namespaces/default | jq ".metadata.annotations[\"meetup\"] = \"$(date)\"" | http PUT http://127.0.0.1:8080/api/v1/namespaces/default HTTP/1.1 200 OK Content-Length: 341 Content-Type: application/json Date: Fri, 10 Mar 2017 08:28:01 GMT
  6. /apis/batch/v2alpha1/jobs Apigroup Version Resource HTTP paths: { “apiVersion“: “v2alpha1“, “kind“:

    “Job“, “metadata“: { “name“: “backup“ }, “spec“: { ... } } http POST /namespaces/<name>/
  7. { "apiVersion": "v1", "kind": "Status", "metadata": {}, "code": 409, "message":

    “...", "status": "Failure“ } /apis/batch/v2alpha1/jobs { “apiVersion“: “v2alpha1“, “kind“: “Job“, “metadata“: { “name“: “backup“ }, “spec“: { ... } } Resource vs. Kind http path vs. logical object * I omitted the namespace in /apis/batch/v1/jobs/namespaces/default/backup
  8. Custom Resource eyjafjallajökull-volcano.yaml apiVersion: belgium.meetup.com/v1 kind: Volcano metadata: name: eyjafjallajökull

    spec: height: 1666 location: Suðurland, Iceland type: Stratovolcano status: lastEruption: 2010-03-26T15:13:42.05Z active: true We want to store these
  9. Custom Resource eyjafjallajökull-volcano.yaml apiVersion: iceland.meetup.com/v1 kind: Volcano metadata: name: eyjafjallajökull

    spec: height: 1666 location: Suðurland, Iceland type: Stratovolcano status: lastEruption: 2010-03-26T15:13:42.05Z active: true We want to store these
  10. Custom Resource Definition (CRD) apiextensions/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata:

    name: volcanos.iceland.meetup.com spec: group: iceland.meetup.com version: v1 names: kind: Volcano plural: volcanos scope: Namespaced must match Defines how CRs are stored
  11. Custom Resource Definition (CRD) apiextensions/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata:

    name: volcanos.iceland.meetup.com spec: group: iceland.meetup.com version: v1 names: kind: Volcano plural: volcanos scope: Namespaced $ kubectl create –f volcanos-crd.yaml a moment ... status: acceptedNames: kind: Volcano listKind: VolcanoList plural: volcanos singular: volcano conditions: - type: NamesAccepted message: no conflicts found reason: NoConflicts status: "True“ - type: Established message: the initial names have been accepted reason: InitialNamesAccepted status: "True“ must match
  12. $ kubectl get volcanos –v=7 • I0429 21:17:53.042783 66743 round_trippers.go:383]

    GET https://localhost:6443/apis • I0429 21:17:53.135811 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1 • I0429 21:17:53.138353 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1/namespaces/default/volcanos No resources found. volcanos → kind Volcano resource volcanos discovery LIST API group iceland.meetup.com/v1 We call this "REST mapping"
  13. $ http localhost:8080/apis/ { "groups": [{ "name": "iceland.meetup.com", "preferredVersion": {"groupVersion":

    "iceland.meetup.com/v1", "version": "v1“}, "versions": [{"groupVersion": "iceland.meetup.com/v1", "version": "v1"}] }, ...] } $ http localhost:8080/apis/iceland.meetup.com/v1 { "apiVersion": "v1", "groupVersion": "iceland.meetup.com/v1", "kind": "APIResourceList", "resources": [{ "kind": "Volcano", "name": "volcanos", "namespaced": true, "verbs": ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch“ ] }, ...] } resource name ⇒ /apis/iceland.meetup.com/v1/volcanos /namespaces/<name>/
  14. $ kubectl get volcanos –v=7 • I0429 21:17:53.042783 66743 round_trippers.go:383]

    GET https://localhost:6443/apis • I0429 21:17:53.135811 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1 • I0429 21:17:53.138353 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1/namespaces/default/volcanos No resources found. volcanos → kind Volcano resource volcanos discovery LIST API group iceland.meetup.com/v1 We call this "REST mapping"
  15. $ kubectl get jökla –v=7 • I0429 21:17:53.042783 66743 round_trippers.go:383]

    GET https://localhost:6443/apis • I0429 21:17:53.135811 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1 • I0429 21:17:53.138353 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1/namespaces/default/volcanos No resources found. jökla → kind Volcano resource volcanos discovery LIST API group iceland.meetup.com/v1 We call this "REST mapping" note: a "shortName"
  16. $ kubectl get jokla –v=7 • I0429 21:17:53.042783 66743 round_trippers.go:383]

    GET https://localhost:6443/apis • I0429 21:17:53.135811 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1 • I0429 21:17:53.138353 66743 round_trippers.go:383] GET https://localhost:6443/apis/iceland.meetup.com/v1/namespaces/default/volcanos No resources found. jokla → kind Volcano resource volcanos discovery LIST API group iceland.meetup.com/v1 We call this "REST mapping" note: a "shortName"
  17. $ kubectl create –f volcanos-crd.yaml $ kubectl create –f eyjafjallajökull-volcano.yaml

    apiVersion: iceland.meetup.com/v1 kind: Volcano metadata: name: eyjafjallajökull spec: height: 1666 location: Suðurland, Iceland type: Stratovolcano status: lastEruption: 2010-03-26T15:13:42.05Z apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: volcanos.iceland.meetup.com spec: group: iceland.meetup.com version: v1 names: kind: Volcano plural: volcanos scope: Namespaced 1 2 Create the CRD Create CustomResources
  18. $ kubectl create –f volcanos-crd.yaml $ kubectl create –f eyjafjallajökull-volcano.yaml

    apiVersion: iceland.meetup.com/v1 kind: Volcano metadata: name: eyjafjallajökull spec: height: 1666 location: Suðurland, Iceland type: Stratovolcano status: lastEruption: 2010-03-26T15:13:42.05Z apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: volcanos.iceland.meetup.com spec: group: iceland.meetup.com version: v1 names: kind: Volcano plural: volcanos scope: Namespaced 1 2 Create the CRD Create CustomResources Follow spec+status pattern.
  19. $ kubectl get volcanos –w --no-headers katla <none> {"apiVersion":"iceland.meetup.com/v1","kind":"Volcanos",... eyjafallajökull

    <none> {"apiVersion":"iceland.meetup.com/v1","kind":" Volcanos ",... $ curl -f 'http://127.0.0.1:8080/apis/iceland.meetup.com/v1/namespaces/default/volcanos?watch=true&resourceVersion=434‘ {"type":"DELETED","object":{"apiVersion":"iceland.meetup.com/v1","kind":"Volcanos","metadata":{"name":"katla","names pace":"default","selfLink":"/apis/iceland.meetup.com/v1/namespaces/default/volcanos/katla","uid":"8f5312c0-29c8-11e7- 88f9-4c3275978b79","resourceVersion":"435","creationTimestamp":"2017-04-25T15:05:03Z"},"spec":{...}}} {"type":"ADDED","object":{"apiVersion":"iceland.meetup.com/v1","kind":"Volcanos","metadata":{"name":"eyjafallajökull"," namespace":"default","selfLink":"/apis/iceland.meetup.com/v1/namespaces/default/volcanos/eyjafallajökull","uid":"b4318 cb5-29c8-11e7-88f9-4c3275978b79","resourceVersion":"436","creationTimestamp":"2017-04-25T15:06:05Z"},"spec":{...}}} Watch, i.e. event stream
  20. Validation • The standard: OpenAPI v3 schema • based on

    JSON Schema apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: volcanos.iceland.meetup.com spec: group: iceland.meetup.com version: v1 names: ... validation: openAPIV3Schema: <see next slide>
  21. spec: type: Stratovolcano height: 1666 location: Suðurland, Iceland status: lastEruption:

    2010-03-26T... active: true properties: spec: properties: type: anyOf: [{"pattern": "^Stratovolcano$"}, …] height: {"type": "integer", "minimum": 0} location: {"type": "string", "default": "Iceland"} required: ["type", "height", "location"] status: properties: active: {"type": "bool"}, lastEruption: {"type": "string", "pattern": "^[0-9]+-...$"}, required: [] OpenAPI v3 Schema a quantor (anyOf, oneOf, allOf exist) note: enum is forbidden (why?) regular expression probably in 1.12+ Custom Resource Helpful tools: kubernetes/kube-openapi#37 tamalsaha/kube-openapi-generator Some other tool from prometheus-operator? Rancher has another one, speak to @lemonjet
  22. Zoom into apiextensions-apiserver kube-apiserver kube- aggregator kube resources apiextensions-apiserver 404

    etcd "delegation" "aggregation" authn/z CR handlers CR handlers CR handlers ⟲Naming Controller ⟲CRD Finalizer request conversion & defaulting storage conversion & defaulting REST logic result conversion validation admission decoding encode GET CREATE LIST UPDATE DELETE WATCH mutating webhooks validating webhooks NoOps json.Unmarshal
  23. "CRDs are limited" • no version conversion (only one version

    possible per CRD) • in 1.11 multiple versions, but no conversion • in 1.12+ conversions come, but slowly • no defaulting (PR exists) • no validation (beta in 1.9, Google Summer of Code project) • no subresources (scale, status) (beta in 1.11) • no admission (since 1.7: admission webhooks + initializers) • alpha CRDs are beta • no custom printing in kubectl (in 1.11: custom printer columns) • demand is high • many users: 300 people listening to CRD talks @ KubeCon EU 2018
  24. $ while true; do http GET http://127.0.0.1:8080/api/v1/namespaces/default | jq ".metadata.annotations[\"meetup\"]

    = \"$(date)\"" | http --check-status PUT http://127.0.0.1:8080/api/v1/namespaces/default || break done ⟲ 1. read object (preferably event driven with watches) 2. change object, update the world 3. update object on apiserver 4. repeat
  25. $ while true; do http GET http://127.0.0.1:8080/api/v1/namespaces/default | {jq ".metadata.annotations[\"meetup\"]

    = \"$RANDOM\""; sleep 1;} | http --check-status PUT http://127.0.0.1:8080/api/v1/namespaces/default || break done HTTP/1.1 409 Conflict Content-Length: 310 Content-Type: application/json Date: Fri, 10 Mar 2017 08:27:58 GMT { "apiVersion": "v1", "code": 409, "details": { "kind": "namespaces", "name": "default“ }, "kind": "Status", "message": "Operation cannot be fulfilled on namespaces \"default\": the object has been modified; please apply your changes to the latest version and try again", "metadata": {}, "reason": "Conflict", "status": "Failure“ } „optimistic concurrency“
  26. $ while true; do http GET http://127.0.0.1:8080/api/v1/namespaces/default | jq ".metadata.annotations[\"meetup\"]

    = \"$(date)\"" | http --check-status PUT http://127.0.0.1:8080/api/v1/namespaces/default || break done ⟲ 1. read object (preferably event driven with watches) 2. change object, update the world 3. update object on apiserver 4. repeat expect version conflicts, remember: optimistic concurrency
  27. $ kubectl get namespaces --watch --no-headers | while read NS

    STATUS TIME ; do # do whatever you like here, e.g. change the namespace echo "$NS changed“ done ⟲ W atch = nohot looping $ curl -f 'http://127.0.0.1:8080/api/v1/namespaces?watch=true&resourceVersion=4711‘ {"type":"ADDED","object":{"kind":"Namespace","apiVersion":"v1","metadata":{"name ... {"type":“MODIFIED","object":{"kind":"Namespace","apiVersion":"v1","metadata":{"name ... {"type":“DELETED","object":{"kind":"Namespace","apiVersion":"v1","metadata":{"name ...
  28. Outlook – Custom Resources • Kubernetes 1.11+ • ⍺: Multiple

    versions without conversion – design proposal • ⍺: Server Side Printing Columns – “kubectl get” customization – #60991 • β: Subresources – ⍺ since 1.10 – #62786 • OpenAPI additionalProperties allowed now (mutually exclusive with properties) • Kubernetes 1.12+ • Multiple versions with declarative field renames • ⍺: Graceful Deletion – being discussed – #63162 • ⍺: Pruning – in validation spec unspecified fields are removed – blocker for GA • ⍺: Defaulting – defaults from OpenAPI validation schema are applied • Strict create mode? Discuss: #5889 – my favorite CRD UX issue Related: CRD OpenAPI validation spec not served by kube-apiserver