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:
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:
The installation should be painless. After installation has completed, you should be able to do a check that Kubernetes is running:
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):
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. 😛
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:
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.