More building blocks

Last modified by Mitchell on 2022/01/26 01:55

As I'd mentioned before, one thing I've been picked up over the past few years is Kubernetes. By default, it's designed for large-scale deployments with a declarative approach toward configuration. There are a number of attractive features that it offers, even for small installations, however, such as:

  • Easier upgrading
    • Delete a pod and it'll redeploy itself with whatever the newest Docker container is - with the caveat that you're using upstream Docker containers, though.
  • Better separation of configuration and data versus executables
    • A single YAML file can contain the entire service configuration, and directories can be mapped in for persistent data.
  • Better scalability
    • Virtual machines inherently use more resources than containers due to needing to emulate more of the stack.

There is, admittedly, more complexity involved in maintaining such a system, but it's still manageable, and I feel that in this case the benefits outweigh the costs. So, what's involved? (Note: I'm not going to be spending much time explaining how Kubernetes works here, since that would end up taking entirely too much time. There are quite a few tutorials out there if you're interested in learning.)

Having looked around at a couple of alternatives, I've found that I'm fairly happy with k3s, which is an abbreviated Kubernetes (also known as k8s) (and, yes, that's a rather awful joke). For now, I've been using a single-node deployment, although it's pretty easy to scale it out as well. The recommend way of installing it using curl, largely for safety reasons:

$ curl -sfL https://get.k3s.io | sh -

If you're feeling brave and/or trust Rancher (the company behind k3s) enough and/or don't feel like installing curl, you can use wget instead:

$ wget -qO - https://get.k3s.io | sh -

The installation should be painless. After installation has completed, you should be able to do a check that Kubernetes is running:

$ kubectl get -n kube-system pods
NAME                                      READY   STATUS              RESTARTS   AGE
metrics-server-6d684c7b5-jl842            0/1     ContainerCreating   0          11s
coredns-d798c9dd-k9rrc                    0/1     ContainerCreating   0          11s
local-path-provisioner-58fb86bdfd-2xvbp   0/1     ContainerCreating   0          11s
helm-install-traefik-2x8c7                0/1     ContainerCreating   0          11s

At this point, we can use a modified simple YAML file from Docker introducing Kubernetes support within Docker (the changes are to migrate deployments from apps/v1beta to app/v1 and to use an Ingress instead of a NodePort):

docker-demo.yaml
apiVersion: v1
kind: Service
metadata:
 name: db
 labels:
   app: words-db
spec:
 ports:
    - port: 5432
     targetPort: 5432
     name: db
 selector:
   app: words-db
 clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: db
 labels:
   app: words-db
spec:
 selector:
   matchLabels:
     app: words-db
 template:
   metadata:
     labels:
       app: words-db
   spec:
     containers:
      - name: db
       image: dockersamples/k8s-wordsmith-db
       ports:
        - containerPort: 5432
         name: db
---
apiVersion: v1
kind: Service
metadata:
 name: words
 labels:
   app: words-api
spec:
 ports:
    - port: 8080
     targetPort: 8080
     name: api
 selector:
   app: words-api
 clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: words
 labels:
   app: words-api
spec:
 replicas: 5
 selector:
   matchLabels:
     app: words-api
 template:
   metadata:
     labels:
       app: words-api
   spec:
     containers:
      - name: words
       image: dockersamples/k8s-wordsmith-api
       ports:
        - containerPort: 8080
         name: api
---
apiVersion: v1
kind: Service
metadata:
 name: web
 labels:
   app: words-web
spec:
 ports:
    - port: 8081
     targetPort: 80
     name: web
 selector:
   app: words-web
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: web
 labels:
   app: words-web
spec:
 selector:
   matchLabels:
     app: words-web
 template:
   metadata:
     labels:
       app: words-web
   spec:
     containers:
      - name: web
       image: dockersamples/k8s-wordsmith-web
       ports:
        - containerPort: 80
         name: words-web
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 name: ingress
spec:
 rules:
  - host: <hostname>
   http:
     paths:
      - path: /
       backend:
         serviceName: web
         servicePort: 8081

I'm also rather fond of the k3s demo put together by a Rancher support engineer, but it doesn't work correctly on newer versions of k3s due to Traefik now setting /ping as a keepalive port (which collides with this app's querying of that URL). It's not too hard to fix it, but it requires either building yet another Docker container (minor changes needed to both main.go and templates/index.html.tmpl) or disabling Traefik's use of /ping, and I don't quite care enough to jump through those hoops. 😛

k3s-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
 name: rancher-demo
 labels:
   app: rancher-demo
spec:
 replicas: 1
 selector:
   matchLabels:
     app: rancher-demo
 template:
   metadata:
     labels:
       app: rancher-demo
   spec:
     containers:
      - name: rancher-demo
       image: superseb/rancher-demo
       ports:
        - containerPort: 8080
---
kind: Service
apiVersion: v1
metadata:
 name: rancher-demo-service
spec:
 selector:
   app: rancher-demo
 ports:
  - protocol: TCP
   port: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 name: rancher-demo-ingress
spec:
 rules:
  - host: <hostname>
   http:
     paths:
      - path: /
       backend:
         serviceName: rancher-demo-service
         servicePort: 8080

The <hostname> in the Ingress specified at the end should be updated with your test domain name. Write it to disk, then load it up into Kubernetes. We can see the components loading up shortly afterward:

$ kubectl apply -f kube-deployment.yml
service/db created
deployment.apps/db created
service/words created
deployment.apps/words created
service/web created
deployment.apps/web created
ingress.extensions/ingress created
$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
db-77f4c964c-9xsq6      1/1     Running   0          4s
web-7fdc45cb65-m744g    1/1     Running   0          4s
words-8b7cc4ff8-j2m2l   1/1     Running   0          4s
words-8b7cc4ff8-2v7xf   1/1     Running   0          4s
words-8b7cc4ff8-fjcgw   1/1     Running   0          4s
words-8b7cc4ff8-x5gtb   1/1     Running   0          4s
words-8b7cc4ff8-cfqdt   1/1     Running   0          4s

At that point, you can go to your domain and verify that it works. The different pods are used as a pool to query nouns/verbs/adjectives, with the IP of the serving pod listed at the bottom of each block. Each reload should show a different set of words/IPs. Congratulations! Although, you probably do want to secure your system, but that's an exercise left up to the reader.