> [!file]+ 30-deployment.yaml > ```yaml > apiVersion: apps/v1 > kind: Deployment > metadata: > name: smp-164-deployment > namespace: mcd > labels: > app: mcd > style: smp > version: 1.6.4 > spec: > replicas: 1 > selector: > matchLabels: > app: mcd > style: smp > version: 1.6.4 > strategy: > type: Recreate > template: > metadata: > labels: > app: mcd > style: smp > version: 1.6.4 > spec: > initContainers: > - name: init-config-files > image: busybox:1.36 > command: ["/bin/sh", "-c"] > args: > - | > yes n | cp -Lri /tmp/config/. /data/ > volumeMounts: > - mountPath: /data > name: smp-164-vol > - mountPath: /tmp/config > name: smp-164-config > #- name: init-compare-files > # image: busybox:1.36 > # command: > # - "find" > # - "/data" > # - "/tmp/config" > # volumeMounts: > # - mountPath: /data > # name: smp-164-vol > # - mountPath: /tmp/config > # name: smp-164-config > #- name: init-sleep > # image: busybox:1.36 > # command: > # - "sleep" > # - "infinity" > # volumeMounts: > # - mountPath: /data > # name: smp-164-vol > # - mountPath: /tmp/config > # name: smp-164-config > containers: > - name: smp-164 > image: itzg/minecraft-server > stdin: true > tty: true > ports: > - containerPort: 25565 > hostPort: 25565 > protocol: TCP > volumeMounts: > - mountPath: /data > name: smp-164-vol > env: > # things the ct won't run without > - name: EULA > value: "true" > > # jvm options > - name: INIT_MEMORY > value: 1G > - name: MAX_MEMORY > value: 3G > - name: JVM_XX_OPTS > value: -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:MaxGCPauseMillis=100 -XX:+DisableExplicitGC -XX:TargetSurvivorRatio=90 -XX:G1NewSizePercent=50 -XX:G1MaxNewSizePercent=80 -XX:G1MixedGCLiveThresholdPercent=50 -XX:+AlwaysPreTouch -XX:+UseLargePages -XX:LargePageSizeInBytes=2m > > # game info > - name: TYPE > value: VANILLA > - name: VERSION > value: 1.6.4 > > # game server options > - name: ENABLE_ROLLING_LOGS > value: "true" > - name: TZ > value: America/Vancouver > > restartPolicy: Always > volumes: > - name: smp-164-vol > persistentVolumeClaim: > claimName: smp-164-pvc > - name: smp-164-config > configMap: > name: smp-164-config > ``` This is where the magic happens. Let's start from the top. # Preamble ```yaml apiVersion: apps/v1 kind: Deployment ``` Nothing fancy here. # Metadata ```yaml metadata: name: smp-164-deployment namespace: mcd labels: app: mcd style: smp version: 1.6.4 ``` Name and namespace should be pretty familiar by now. Even though we're not managing a complex deployment here, I applied a few labels to help identify what this particular service is: it's a minecraft daemon (`mcd`), configured for survival multiplayer (`smp`), running version `1.6.4`. This way, we can grep for things in the future based on any of those dimensions and see what we have running in the field. I probably should have also added a label like `track: vanilla` to help differentiate from Paper and the like. Note that these labels are applied to the _Deployment_ resource itself, not the pod(s) in the deployment. ```yaml spec: replicas: 1 ``` This deployment should be running one (1) replica: just a single instance of the server. If we were running a major web service, we would probably want to run many replicas to help distribute the load across machines. ```yaml selector: matchLabels: app: mcd style: smp version: 1.6.4 ``` How do we know if we're running the right number of replicas? Look for pods with these labels and count them up. Note! We just saw these labels a minute ago, but they are _not_ the ones we're matching against! (I think!) They just happen to be identical because the scope of this deployment is very limited, and I'm a noob and don't know how to choose useful labels yet. I'm pretty sure these are compared with the labels that apply to _Pods_, which we'll define a couple of blocks from now. ```yaml strategy: type: Recreate ``` Dunno. ```yaml template: ``` From here on down, we describe a pod. ```yaml metadata: labels: app: mcd style: smp version: 1.6.4 ``` Here are those pod labels we talked about. If I've got this right, these are how the deployment controller knows when to spin up more pods. ```yaml spec: initContainers: ``` A pod can run multiple containers, but it can also run _init containers_. These run to completion before the job containers start. This is how we can configure the pod (remember, it's basically a VM with a filesystem and stuff) to be just right for the actual server(s) to run in. ```yaml - name: init-config-files image: busybox:1.36 command: ["/bin/sh", "-c"] args: - | yes n | cp -Lri /tmp/config/. /data/ volumeMounts: - mountPath: /data name: smp-164-vol - mountPath: /tmp/config name: smp-164-config ``` In our case, remember how the config map is read-only but we need to write to it? Here's the solution: Mount the volume and the config map in an init container, and copy everything out into the volume. Busybox's cp's `-n` flag doesn't seem to work ("don't overwrite", or "no clobber"), but we can emulate it with `yes n | cp -i`. I learned that `cp foo/. bar/` lets you copy everything from inside of `foo/` to inside of `bar/` without creating `bar/foo/`. Note that the `name`s in the `volumeMounts` section are _not_ the names we supplied in the yaml to define the PVC and config map! Instead, they are (once again) declared later on in this file (at the end). By the way, the reason we need something like `cp -n` is because these init containers will run every time the pod is spun up. If we've shut down the server for a bit and want to start it back up, the server will have made its own changes to the config files, and we don't want to overwrite those. `-L` is because the config map doesn't just mount a file straight up, it's a link to a file in a directory that's a link to another directory that has the actual file in it. I kept the `-r` because there are other config files we could put in there if we wanted them copied into a clean server as well, such as a list of users to grant op powers. ```yaml #- name: init-compare-files # image: busybox:1.36 # command: # - "find" # - "/data" # - "/tmp/config" # volumeMounts: # - mountPath: /data # name: smp-164-vol # - mountPath: /tmp/config # name: smp-164-config ``` It took a lot of trial and error to learn that tidbit about `-n`. I added a couple more init containers that run after the cp happens so that I could inspect the results before starting the server. This one prints the contents of the destination and the source to stdout, which appears in the pod's logs that I can view in Infra. ```yaml #- name: init-sleep # image: busybox:1.36 # command: # - "sleep" # - "infinity" # volumeMounts: # - mountPath: /data # name: smp-164-vol # - mountPath: /tmp/config # name: smp-164-config ``` Another trick I learned: if you have a pod that's crashing, you can't `exec` to get a shell in it if it's stopped. We can set up a job that [sleeps forever](https://stackoverflow.com/a/62099250/6627273) to keep the pod alive, then `exec` into a shell in this init container and poke around the filesystem ourselves to see what happened (and run our own experiments). ```yaml containers: - name: smp-164 image: itzg/minecraft-server stdin: true tty: true ``` Finally! Let's configure the actual server container. Give it a name, an image, tell it to expose stdin and tty because it runs a CLI, normal stuff you'll recognize from Docker Compose. ```yaml ports: - containerPort: 25565 hostPort: 25565 protocol: TCP ``` This should also look familiar, but it's not quite what it seems. This does not expose the container port 25565 as the port 25565 on the node, facing the internet. I think `hostPort` refers to a port on the _pod_ (remember: basically a VM). That's the port that our Service will connect to downstream. ```yaml volumeMounts: - mountPath: /data name: smp-164-vol ``` We don't need the config map anymore since we copied everything out of it, so we won't mount it in the server container. We just need our PVC, and we're gonna mount it at `/data` because that's where the container image wants it. ```yaml env: # things the ct won't run without - name: EULA value: "true" # jvm options - name: INIT_MEMORY value: 1G - name: MAX_MEMORY value: 3G - name: JVM_XX_OPTS value: -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:MaxGCPauseMillis=100 -XX:+DisableExplicitGC -XX:TargetSurvivorRatio=90 -XX:G1NewSizePercent=50 -XX:G1MaxNewSizePercent=80 -XX:G1MixedGCLiveThresholdPercent=50 -XX:+AlwaysPreTouch -XX:+UseLargePages -XX:LargePageSizeInBytes=2m # game info - name: TYPE value: VANILLA - name: VERSION value: 1.6.4 # game server options - name: ENABLE_ROLLING_LOGS value: "true" - name: TZ value: America/Vancouver ``` This is all stuff specific to the [`itzg/minecraft-server`](https://docker-minecraft-server.readthedocs.io/en/latest/) image. We're setting up these environment variables to pass into the container. ```yaml restartPolicy: Always ``` Same as `restart: always` in Docker Compose, probably. Not entirely sure of the implications in k8s or what the other options look like. ```yaml volumes: - name: smp-164-vol persistentVolumeClaim: claimName: smp-164-pvc - name: smp-164-config configMap: name: smp-164-config ``` Here we go, this maps the volumes (both the [PVC](320-pvc) and the [ConfigMap](330-configmap)) that we identify by their `kind` and `name`, into names that we can reference when specifying container volumes above. I should probably move this higher up in the file for clarity.