|
| 1 | +# Testing Kubernetes CRD versions |
| 2 | + |
| 3 | +One interesting thing to see when working with CRDs `storage` and `served` flags is that you can actually query different versions of the same resource. |
| 4 | + |
| 5 | +This example will work in Kubernetes above 1.15 and below 1.21. If you're using `kind`, you should get a cluster, at the time of writing this document, with Kubernetes 1.21. |
| 6 | + |
| 7 | +Copy the ["Minimal example" from the Ingress documentation page of Kubernetes](https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource), copied below: |
| 8 | + |
| 9 | +```yaml |
| 10 | +apiVersion: networking.k8s.io/v1 |
| 11 | +kind: Ingress |
| 12 | +metadata: |
| 13 | + name: minimal-ingress |
| 14 | + annotations: |
| 15 | + nginx.ingress.kubernetes.io/rewrite-target: / |
| 16 | +spec: |
| 17 | + rules: |
| 18 | + - http: |
| 19 | + paths: |
| 20 | + - path: /testpath |
| 21 | + pathType: Prefix |
| 22 | + backend: |
| 23 | + service: |
| 24 | + name: test |
| 25 | + port: |
| 26 | + number: 80 |
| 27 | +``` |
| 28 | +
|
| 29 | +Now we know it won't work because we don't have a `Service` called `test`, but we will use it to show some interesting behaviour. |
| 30 | + |
| 31 | +On Kubernetes 1.21, when you query an `Ingress`, you might get the following message: |
| 32 | + |
| 33 | +``` |
| 34 | +Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress |
| 35 | +``` |
| 36 | + |
| 37 | +We can use it to play with it and versions of the ingress above. Save the contents of the ingress into a file called `ingress.yaml`, then apply it: |
| 38 | + |
| 39 | +``` |
| 40 | +$ kubectl apply -f ingress.yaml |
| 41 | +ingress.networking.k8s.io/minimal-ingress created |
| 42 | +``` |
| 43 | + |
| 44 | +Now, let's query multiple versions of this object. Since the original one has the following version data: |
| 45 | + |
| 46 | +```yaml |
| 47 | +apiVersion: networking.k8s.io/v1 |
| 48 | +kind: Ingress |
| 49 | +``` |
| 50 | + |
| 51 | +We can use that, plus `kubectl`, to query it especifically by version. The `kubectl get` help content says that the usage can be either of the following (see `kubectl get -h`): |
| 52 | + |
| 53 | +```bash |
| 54 | +kubectl get TYPE[.VERSION][.GROUP] [NAME | -l label] |
| 55 | +kubectl get TYPE[.VERSION][.GROUP]/NAME |
| 56 | +``` |
| 57 | + |
| 58 | +So let's use the first one, which is: |
| 59 | + |
| 60 | +```bash |
| 61 | +$ kubectl get ingress.v1.networking.k8s.io minimal-ingress |
| 62 | +NAME CLASS HOSTS ADDRESS PORTS AGE |
| 63 | +minimal-ingress <none> * 80 11m |
| 64 | +``` |
| 65 | + |
| 66 | +This works. How about... |
| 67 | + |
| 68 | +```bash |
| 69 | +$ kubectl get ingress.v1beta1.extensions minimal-ingress |
| 70 | +Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress |
| 71 | +NAME CLASS HOSTS ADDRESS PORTS AGE |
| 72 | +minimal-ingress <none> * 80 11m |
| 73 | +``` |
| 74 | + |
| 75 | +... This also works! Although we received a warning, we are still able to query the version of the Ingress using an old CRD version of it. Let's go one step further. Let's request the resources as YAML (and we will use a quick `grep` to only show the `spec` part, excluding the `metadata`). For the first one we have: |
| 76 | + |
| 77 | + |
| 78 | +```bash |
| 79 | +kubectl get ingress.v1.networking.k8s.io minimal-ingress -o yaml | grep -A 100 "spec:" |
| 80 | +``` |
| 81 | +```yaml |
| 82 | +spec: |
| 83 | + rules: |
| 84 | + - http: |
| 85 | + paths: |
| 86 | + - backend: |
| 87 | + service: |
| 88 | + name: test |
| 89 | + port: |
| 90 | + number: 80 |
| 91 | + path: /testpath |
| 92 | + pathType: Prefix |
| 93 | +status: |
| 94 | + loadBalancer: {} |
| 95 | +``` |
| 96 | + |
| 97 | +As expected, we get the response we were hoping for. I mean, we created an Ingress using this specific version, version `v1` of the group `networking.k8s.io`. But since we were able to retrieve it before using the old version, will we be able to retrieve the YAML for this old version too? Will it be like old ingresses or will it be like the new ingresses? Let's see: |
| 98 | + |
| 99 | +```bash |
| 100 | +kubectl get ingress.v1beta1.extensions minimal-ingress -o yaml | grep -A 100 "spec:" |
| 101 | +``` |
| 102 | + |
| 103 | +```yaml |
| 104 | +Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress |
| 105 | +spec: |
| 106 | + rules: |
| 107 | + - http: |
| 108 | + paths: |
| 109 | + - backend: |
| 110 | + serviceName: test |
| 111 | + servicePort: 80 |
| 112 | + path: /testpath |
| 113 | + pathType: Prefix |
| 114 | +status: |
| 115 | + loadBalancer: {} |
| 116 | +``` |
| 117 | + |
| 118 | +Wait... This doesn't look at all how we submitted it ourselves! We defined a `spec.rules[0].http.paths[0].backend.service.name` with the value of `test`, not a `spec.rules[0].http.paths[0].backend.serviceName`! |
| 119 | + |
| 120 | +Was this done by the API or by `kubectl`? Let's see. Let's proxy the API locally: |
| 121 | + |
| 122 | +```bash |
| 123 | +kubectl proxy |
| 124 | +``` |
| 125 | + |
| 126 | +And now, let's run the previous commandds with verbose mode enabled at level 8, and grab that log output sent to `stderr`, pipe it to `stdout` and find the `HTTP GET` call done by `kubectl`: |
| 127 | + |
| 128 | +```bash |
| 129 | +kubectl get ingress.v1.networking.k8s.io minimal-ingress -v=8 2>&1 | grep "minimal-ingress" |
| 130 | +``` |
| 131 | + |
| 132 | +``` |
| 133 | +I1214 16:19:47.039228 75843 round_trippers.go:432] GET https://127.0.0.1:37691/apis/networking.k8s.io/v1/namespaces/default/ingresses/minimal-ingress |
| 134 | +minimal-ingress <none> * 80 23m |
| 135 | +``` |
| 136 | +
|
| 137 | +And now let's call that endpoint, but against our proxy: |
| 138 | +
|
| 139 | +```bash |
| 140 | +curl -s localhost:8001/apis/networking.k8s.io/v1/namespaces/default/ingresses/minimal-ingress | jq .spec |
| 141 | +``` |
| 142 | + |
| 143 | +```json |
| 144 | +{ |
| 145 | + "rules": [ |
| 146 | + { |
| 147 | + "http": { |
| 148 | + "paths": [ |
| 149 | + { |
| 150 | + "path": "/testpath", |
| 151 | + "pathType": "Prefix", |
| 152 | + "backend": { |
| 153 | + "service": { |
| 154 | + "name": "test", |
| 155 | + "port": { |
| 156 | + "number": 80 |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + ] |
| 162 | + } |
| 163 | + } |
| 164 | + ] |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +Ok, looks good. Let's look at the new one, which we can guess the URL, but let's do it still nonetheless. First, get the API Endpoint call using `kubectl` verbose mode: |
| 169 | + |
| 170 | +```bash |
| 171 | +kubectl get ingress.v1beta1.extensions minimal-ingress -v=8 2>&1 | grep "minimal-ingress" |
| 172 | +``` |
| 173 | + |
| 174 | +``` |
| 175 | +I1214 16:21:07.989565 76368 round_trippers.go:432] GET https://127.0.0.1:37691/apis/extensions/v1beta1/namespaces/default/ingresses/minimal-ingress |
| 176 | +minimal-ingress <none> * 80 24m |
| 177 | +``` |
| 178 | + |
| 179 | +Then let's replace the host for our proxy, and make the call: |
| 180 | + |
| 181 | +```bash |
| 182 | +curl -s localhost:8001/apis/extensions/v1beta1/namespaces/default/ingresses/minimal-ingress | jq .spec |
| 183 | +``` |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "rules": [ |
| 188 | + { |
| 189 | + "http": { |
| 190 | + "paths": [ |
| 191 | + { |
| 192 | + "path": "/testpath", |
| 193 | + "pathType": "Prefix", |
| 194 | + "backend": { |
| 195 | + "serviceName": "test", |
| 196 | + "servicePort": 80 |
| 197 | + } |
| 198 | + } |
| 199 | + ] |
| 200 | + } |
| 201 | + } |
| 202 | + ] |
| 203 | +} |
| 204 | +``` |
| 205 | + |
| 206 | +So `kubectl` didn't do the "conversion", the Kubernetes API did! |
| 207 | + |
| 208 | +Keep in mind this is one edge case scenario [of which not many people are happy](https://github.com/kubernetes/kubernetes/issues/94761). The standard is that custom resources should create their own Conversion logic [using a Conversion Webhook](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion). No conversion is done automagically for 3rd party resources. |
| 209 | + |
| 210 | +Ingresses are handled differently, just because they are ingresses. In better words: |
| 211 | + |
| 212 | +> When you create an ingress object, it can be read via any version (the server handles converting into the requested version). `kubectl get ingress` is an ambiguous request, since it does not indicate what version is desired to be read. |
| 213 | +> |
| 214 | +> When an ambiguous request is made, kubectl searches the discovery docs returned by the server to find the first group/version that contains the specified resource. |
| 215 | +> |
| 216 | +> For compatibility reasons, extensions/v1beta1 has historically been preferred over all other api versions. Now that ingress is the only resource remaining in that group, and is deprecated and has a GA replacement, 1.20 will drop it in priority so that `kubectl get ingress` would read from `networking.k8s.io/v1`, but a 1.19 server will still follow the historical priority. |
| 217 | +> |
| 218 | +> If you want to read a specific version, you can qualify the get request (like `kubectl get ingresses.v1.networking.k8s.io ...`) or can pass in a manifest file to request the same version specified in the file (`kubectl get -f ing.yaml -o yaml`) |
| 219 | +> [#](https://github.com/kubernetes/kubernetes/issues/94761#issuecomment-691982480). |
| 220 | +
|
| 221 | +It's also worth nothing that: |
| 222 | + |
| 223 | +> The API version used to create an object is not intentionally exposed in the API... all ingress objects are available via all served apiVersions, regardless of the version they were created with (the API server converts between versions in order to return ingress objects in the requested version). |
| 224 | +> [#](https://github.com/kubernetes/kubernetes/issues/94761#issuecomment-880900951) |
| 225 | +
|
| 226 | +On all other third-party cases, the [standard Kubernetes CRD versioning rules](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#overview) remain in place. |
0 commit comments