Introduction to YAML, Part 2: Ingress and repeated nodes
Nick Chase - July 24, 2021
In Part 1 of this series, we looked at the basics behind YAML configuration and showed you how to create basic Kubernetes objects such as Pods and Deployments using the two basic structures of YAML, Maps and Lists. Now we're going to look at enhancing your YAML documents with repeated nodes in the context of Kubernetes Services, Endpoints, and Ingress.
Let's start with a basic scalar value.
As for the spec, a Service needs two basic pieces of information: a selector, which identifies Pods that it should work with (in this case, any pods with the label app=nginx) and the ports the service manages. In this case, we have two external ports, both of which get forwarded to port 80 of the actual pod.
So let's make this more convenient. We can create Kubernetes YAML anchors that specifies a value, then use an alias to reference that anchor. For example:
So now let's look at creating an anchor out of one of those port definitions:
In the previous section we looked at replacing entire objects with an alias, but sometimes you want to do that with slight changes. For this Kubernetes ingress YAML example, we might have something that looks like this:
Now if we go ahead and apply this YAML, we can see the results:
Let's start with a basic scalar value.
A simple repeated scalar value in YAML: building a Kubernetes Service YAML file
To see how we can create a simple repeated value, we're going to look at Kubernetes Services. A complete look at Services is beyond the scope of this article, but there are three basic things you need to understand:- Services are how pods communicate in a network environment, either with each other in a Kubernetes cluster or with the outside world. They do this by specifying a port for the caller to use, and a targetPort, which is the port on which the Pod itself receives the message.
- Services know which pods to target based on labels specified in the selector.
- Services come in four different types:
- ClusterIP: The default ServiceType, a ClusterIP service makes the service reachable from within the cluster via a cluster-internal IP.
- NodePort: A NodePort service makes it possible to access a Service by directing requests to a specific port on every Node, accessed via the NodeIP. (Kubernetes automatically creates a ClusterIP service to route the request.) So from outside the cluster, you'd send the request to <NodeIP>:<NodePort>.
- LoadBalancer: In order to deploy a LoadBalancer service, you have to be using a cloud provider that supports it; it's the cloud provider that actually makes this functionality available. This service is running on top of NodePort and ClusterIP services, which Kubernetes creates automatically.
- ExternalName: In production situations, you will likely want to use ExternalName, which maps the service to a CNAME record such as a Fully Qualified Domain Name.
Repeated values with anchors and aliases
In Part 1, we covered the basics of creating Kubernetes objects using YAML, and creating a Service is no different.apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: selector: app: nginx ports: - port: 80 name: http targetPort: 80 - port: 443 name: https targetPort: 80As you can see, we're creating an object just as we did in Part 1, with metadata and a spec. Metadata is the same as it was when we were dealing with Deployments, in that we are specifying information about the object and adding labels to any instances created.
As for the spec, a Service needs two basic pieces of information: a selector, which identifies Pods that it should work with (in this case, any pods with the label app=nginx) and the ports the service manages. In this case, we have two external ports, both of which get forwarded to port 80 of the actual pod.
So let's make this more convenient. We can create Kubernetes YAML anchors that specifies a value, then use an alias to reference that anchor. For example:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: selector: app: nginx ports: - port: &target 80 name: http targetPort: *target - port: 443 name: https targetPort: *targetWe create the anchor with the ampersand (&), as in &target, then reference it with the alias created with the asterisk (*), as in *target. If we were to put this into a file and create it using the kubectl command, we would get a new Service, as we can see:
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 32d nginx ClusterIP 10.107.206.48 <none> 80/TCP,443/TCP 13mIf we then went on to describe the service, we could see that the values carried through:
$ kubectl describe svc nginx Name: nginx Namespace: default Labels: app=nginx Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"p... Selector: app=nginx Type: ClusterIP IP: 10.107.206.48 Port: http 80/TCP TargetPort: 80/TCP Endpoints: <none> Port: https 443/TCP TargetPort: 80/TCP Endpoints: <none> Session Affinity: None Events: <none>Now if we wanted to change that port, we could do it simply by changing the anchor:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: selector: app: nginx ports: - port: &target 88 name: http targetPort: *target - port: 443 name: https targetPort: *targetWe can then apply the changes...
$ kubectl apply -f test.yaml service/nginx configured... and look at the newly configured service:
$ kubectl describe svc nginx Name: nginx Namespace: default Labels: app=nginx Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"p... Selector: app=nginx Type: ClusterIP IP: 10.107.206.48 Port: http 88/TCP TargetPort: 88/TCP Endpoints: <none> Port: https 443/TCP TargetPort: 88/TCP Endpoints: <none> Session Affinity: None Events: <none>As you can see, all three values were changed by simply changing the anchor in our Kubernetes service YAML configuration . Handy, but fortunately, we can also create anchors for more complicated structures.
Anchors for non-scalars: Creating Endpoints
Endpoints are, as in other applications, the target to which you'll send your requests in order to access an application. Kubernetes creates them automatically, but you can also create them manually and link them to a specific service. For example:apiVersion: v1 kind: Endpoints metadata: name: mytest-cluster subsets: - addresses: - ip: 192.168.10.100 ports: - name: myport port: 1 protocol: TCP - addresses: - ip: 192.168.10.101 ports: - name: myport port: 1 protocol: TCP - addresses: - ip: 192.168.10.102 ports: - name: myport port: 1 protocol: TCPAs you can see, what you have here is the basic structure, only instead of a spec, we have subsets, each of which consists of one or more IP addresses and the ports to access them.
So now let's look at creating an anchor out of one of those port definitions:
apiVersion: v1 kind: Endpoints metadata: name: mytest-cluster subsets: - addresses: - ip: 192.168.10.100 ports: &stdport - name: myport port: 1 protocol: TCP - addresses: - ip: 192.168.10.101 ports: *stdport - addresses: - ip: 192.168.10.102 ports: *stdportIf we describe the endpoints we can see that they've been created as we expect:
$ kubectl describe endpoints mytest-cluster Name: mytest-cluster Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Endpoints","metadata":{"annotations":{},"name":"mytest-cluster","namespace":"default"},"subsets":[{"addresses":... Subsets: Addresses: 192.168.10.100,192.168.10.101,192.168.10.102 NotReadyAddresses: <none> Ports: Name Port Protocol ---- ---- -------- myport 1 TCPBut when you're using an alias for a structure such as this, you'll often want to change a specific value and leave the rest intact. We'll do that next.
Events: <none>
Changing a specific value: Kubernetes Ingress
In this final section, we'll look at creating a Kubernetes Ingress, which makes it simpler to create access to your applications. We'll also look at another aspect of using aliases.In the previous section we looked at replacing entire objects with an alias, but sometimes you want to do that with slight changes. For this Kubernetes ingress YAML example, we might have something that looks like this:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /testpath backend: &stdbe serviceName: test servicePort: 80 - path: /realpath backend: *stdbe - path: /hiddenpath backend: *stdbeIn this case, we have three paths that all point to the same service on the same port. But what if we want to have one path that points to another port? To do that we want to override one of the existing values, like so:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /testpath backend: &stdbe serviceName: test servicePort: 80 - path: /realpath backend: *stdbe - path: /hiddenpath backend: << : *stdbe servicePort: 443Now, a couple of things to note here. First off, the alias represents a value, so it has to have a name. We can't use backend as the name, because we need *stdbe down one level so that we can replace servicePort. So to reference the fact that we're going up one level, we're using the << notation. Then we can add another servicePort value to the same level of the hierarchy.
Now if we go ahead and apply this YAML, we can see the results:
$ kubectl apply -f test.yaml ingress.extensions/test-ingress configuredSo that's anchors and aliases as well as Kubernetes service YAML and ingress YAML configuration files. If you want more information on YAML, including using specific data types, feel free to check out the helpful content in this webinar on YAML and Kubernetes objects.
$ kubectl describe ingress test-ingress Name: test-ingress Namespace: default Address: Default backend: default-http-backend:80 (<none>) Rules: Host Path Backends ---- ---- -------- * /testpath test:80 (<none>) /realpath test:80 (<none>) /hiddenpath test:443 (<none>) Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"nginx.ingress.kubernetes.io/rewrite-target":"/"},"name":"test-ingress","namespace":"default"},"spec":{"rules":[{"http":{"paths":[{"backend":{"serviceName":"test","servicePort":80},"path":"/testpath"},{"backend":{"serviceName":"test","servicePort":80},"path":"/realpath"},{"backend":{"serviceName":"test","servicePort":443},"path":"/hiddenpath"}]}}]}} nginx.ingress.kubernetes.io/rewrite-target: / Events: <none>