Kubernetes Endpoint and externalName Service

It’s been a while since I did not write some Kubernetes things because a few months back I just played with another workload/container orchestration tools, the Hashicorp Nomad and Consul stacks (Will write it someday hahaha ✌️).

From the Hashicorp Nomad and Consul stacks, i just got some miracle about how basic things the workload works, especially when you deal with container stuff, horizontal/vertical scaling or something most people call distributed system and yeah its about DNS (Domain Name System) implementation for your workload mapping and how they call or communicate each others (for this one i learn much from main features of Consul).

I remembered that Kubernetes already owns the internal DNS, and got an idea how if I have an external service like DB’s and external API but i just want to consume or mapping with current Kubernetes internal DNS ? 🤔

First of all I just usually whitelist some ip’s or domain to my cluster then open a few firewalls and done or add legit things with Envoy Proxy (Istio with Egress Gateway). But I was so curious how to do that with native Kubernetes and perhaps the possible way to apply with externalName and Endpoint Service.

Preparing

The first, launch Kubernetes in Docker (Kind) with a small cluster and tweak to enable the ingress.

# kind-config.yaml
---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
    - |
      kind: InitConfiguration
      nodeRegistration:
        kubeletExtraArgs:
          node-labels: "ingress-ready=true"      
    extraPortMappings:
    - containerPort: 80
      hostPort: 80
      protocol: TCP
    - containerPort: 443
      hostPort: 443
      protocol: TCP
  - role: worker
# just want to specify the version at v1.22.xx 🤪
kind create cluster --name abc \
--config kind-config.yaml \
--image kindest/node:v1.22.9

Secondly, launch a virtual machine or container that can run a database and I just did it with this.

# Remember to used --network=kind as a default network from kind
# The reason is: Currently so lazy dealing with internal network route
# So keep it same network with default kind cluster
docker run --rm -p 127.0.0.1:3306:3306 --name mysql \
--network=kind --env MARIADB_USER=user \
--env MARIADB_PASSWORD=111 \
--env MARIADB_ROOT_PASSWORD=123 \
mariadb:10.7.4-focal

For the additional to make sure what my database ip’s, check it with docker network inspect kind , and the output its like this one.

"Containers": {
            "23d326e8787473cd1ee0e479fd38cf362ee4aa62c91d3d74e9e7c64fcb50105b": {
                "Name": "dev-cluster-worker",
                "EndpointID": "37d9b239d7d67aa835227bafaaada74006324ed762dcf538d6e72323fe3fec1b",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": "fc00:f853:ccd:e793::2/64"
            },
            "295d932fc495565f625ccabd6a093991aa88ffe062a98807201102986c25f314": {
                "Name": "mysql",
                "EndpointID": "8996ce8fd25192318e41a9be571c2b184d89b8c85819e8679cafd622269c5e33",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": "fc00:f853:ccd:e793::4/64"
            },
            "7b02453376b8aadd1a904f350b913f5fc524fd955951e07b2ece2abe0d11f7ca": {
                "Name": "dev-cluster-control-plane",
                "EndpointID": "f54966c97f28533c9d5f6b6a9862aaa7a459dd5f22619ba3e4335956e3a6bda7",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": "fc00:f853:ccd:e793::3/64"
            }
        },

The database has a 172.18.0.4 ip address with the same network with KinD cluster (/16) prefixes.

Kubernetes Endpoints

Create Kubernetes Service manifest that defines database ip address and port.

---
apiVersion: v1
kind: Endpoints
metadata:
  name: ext-svc
subsets:
  - addresses:
      - ip: 172.18.0.4 # DB ip
    ports:
      - port: 3306 # DB port
---
apiVersion: v1
kind: Service
metadata:
  name: ext-svc
spec:
  ports:
    - protocol: TCP # It's TCP communication
      port: 3306
      targetPort: 3306

Kubernetes Endpoints usually created automatically when you define Deployment and some Pod’s, its act for upstream the ip address Pod’s. So for the example: having 4 Pods so the list subsets.addresses will having 4 different ip address.

But for this case, we define it manually and Kubernetes Endpoint upstream is the ip address of DB’s.

Spawn new container inside KinD eg: kubectl run --rm -i --tty ubuntu-focal --image=ubuntu:focal -- bash then test it with nc and mysql-client command.

# nc
root@ubuntu-focal:/# nc -zv ext-svc 3306
Connection to ext-svc 3306 port [tcp/*] succeeded!
root@ubuntu-focal:/#

# mysql-client
root@ubuntu-focal:/# mysql -u user -h ext-svc -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.5.5-10.7.4-MariaDB-1:10.7.4+maria~focal mariadb.org binary distribution

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

Kubernetes externalName Service

Next case is how if my external workload or API is domain based ? 🤔

For this case, we will using spec.externalName and about full docs, read carefully here .

Let’s go back to KinD cluster before, I will show the fun way how to use it with Kubernetes Ingress. First install the Ingress with the following steps from official docs .

After that create manifest Service and the Ingress one.


---
apiVersion: v1
kind: Service
metadata:
  name: ext-landing
spec:
  type: ExternalName
  # can be your Db's DNS too
  externalName: httpbin.org

# ingress
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ext-ingress
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: ext-landing
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
status:
  loadBalancer: {}

Type the magical command aka kubectl apply then test it with calling curl command to localhost.

curl -I http://localhost
HTTP/1.1 200 OK
Date: Wed, 13 Jul 2022 17:31:26 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Bump a fun way how to reverse proxy 😃😃

Closing Notes

Thank You!!