Mirantis named a Challenger in 2024 Gartner® Magic Quadrant™ for Container Management  |  Learn More

< BLOG HOME

Introduction to YAML, Part 2: Ingress and repeated nodes

image
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.

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:
  1. 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.
  2. Services know which pods to target based on labels specified in the selector.
  3. 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.
OK, with the basics under our belt, let's take a look at actually creating a Kubernetes Service YAML configuration.

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: 80
As 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: *target
We 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   13m
If 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: *target
We 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: TCP
As 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: *stdport
If 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  TCP

Events:  <none>
But 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.

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: *stdbe
In 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: 443
Now, 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 configured

$ 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>
So 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.
 

Choose your cloud native journey.

Whatever your role, we’re here to help with open source tools and world-class support.

GET STARTED