<p>I created a custom resource definition (CRD) and its controller in my cluster, now I can create custom resources, but how do I validate update requests to the CR? e.g., only certain fields can be updated.</p>

<p>The Kubernetes docs on Custom Resources has a section on <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility" rel="nofollow noreferrer">Advanced features and flexibility</a> (never mind that validating requests should be considered a pretty basic feature 😉). For validation of CRDs, it says:</p>
<blockquote>
<p>Most validation can be specified in the CRD using <a href="https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/#validation" rel="nofollow noreferrer">OpenAPI v3.0 validation</a>. Any other validations supported by addition of a <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook-alpha-in-1-8-beta-in-1-9" rel="nofollow noreferrer">Validating Webhook</a>.</p>
</blockquote>
<p>The OpenAPI v3.0 validation won't help you accomplish what you're looking for, namely ensuring immutability of certain fields on your custom resource, it's only helpful for stateless validations where you're looking at one instance of an object and determining if it's valid or not, you can't compare it to a previous version of the resource and validate that nothing has changed.</p>
<p>You could use Validating Webhooks. It feels like a heavyweight solution, as you will need to implement a server that conforms to the Validating Webhook contract (responding to specific kinds of requests with specific kinds of responses), but you will have the required data at least to make the desired determination, e.g. knowing that it's an UPDATE request and knowing what the old object looked like.  For more details, see <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response" rel="nofollow noreferrer">here</a>. I have not actually tried Validating Webhooks, but it feels like it could work.</p>
<p>An alternative approach I've used is to store the user-provided data within the <code>Status</code> subresource of the custom resource the first time it's created, and then always look at the data there. Any changes to the <code>Spec</code> are ignored, though your controller can notice discrepancies between what's in the <code>Spec</code> and what's in the <code>Status</code>, and embed a warning in the <code>Status</code> telling the user that they've mutated the object in an invalid way and their specified values are being ignored. You can see an example of that approach <a href="https://github.com/amitkgupta/boshv3/blob/e037d2ddddc440004e169869b6143ff8bdd0f84e/api/v1/team_types.go#L76-L94" rel="nofollow noreferrer">here</a> and <a href="https://github.com/amitkgupta/boshv3/blob/01f6a62151a6df8e524241d4a14b08dae195f431/controllers/team_controller.go#L55-L60" rel="nofollow noreferrer">here</a>.  As per the <a href="https://github.com/amitkgupta/boshv3#team-1" rel="nofollow noreferrer">relevant README section</a> of that linked repo, this results in the following behaviour:</p>
<blockquote>
<p>The <code>AVAILABLE</code> column will show false if the UAA client for the team has not been successfully created. The <code>WARNING</code> column will display a warning if you have mutated the Team spec after initial creation. The <code>DIRECTOR</code> column displays the originally provided value for <code>spec.director</code> and this is the value that this team will continue to use. If you do attempt to mutate the <code>Team</code> resource, you can see your (ignored) user-provided value with the<code> -o wide</code> flag:</p>
<pre><code>$ kubectl get team --all-namespaces -owide
NAMESPACE   NAME   DIRECTOR     AVAILABLE   WARNING   USER-PROVIDED DIRECTOR
test        test   vbox-admin   true                  vbox-admin
</code></pre>
<p>If we attempt to mutate the <code>spec.director</code> property, here's what we will see:</p>
<pre><code>$ kubectl get team --all-namespaces -owide
NAMESPACE   NAME   DIRECTOR     AVAILABLE   WARNING                                              USER-PROVIDED DIRECTOR
test        test   vbox-admin   true        API resource has been mutated; all changes ignored   bad-new-director-name
</code></pre>
</blockquote>
