|
| 1 | +# Minikube |
| 2 | + |
| 3 | +Minikube is a tool for running a single-node kubernetes cluster inside of a virtual machine. It is a popular tool for developing Kubernetes applications locally. |
| 4 | + |
| 5 | +This topic will cover using `minikube` to set up the project Kubernetes locally. |
| 6 | + |
| 7 | +I'll be following [this guide](https://medium.com/@markgituma/kubernetes-local-to-production-with-django-1-introduction-d73adc9ce4b4) to get started. |
| 8 | + |
| 9 | +## Getting started |
| 10 | + |
| 11 | +### Start minikube |
| 12 | + |
| 13 | +To get started, bring up `minikube` with |
| 14 | + |
| 15 | +```bash |
| 16 | +minikube start |
| 17 | +``` |
| 18 | + |
| 19 | +Optionally, run `minikube delete`, and then `minikube start` to start with a clean cluster. |
| 20 | + |
| 21 | +I'll be using the following alias to use `kubectl`: |
| 22 | + |
| 23 | +```bash |
| 24 | +alias k='kubectl' |
| 25 | +``` |
| 26 | + |
| 27 | +## Build the Django server Deployment |
| 28 | + |
| 29 | +We need to build our `backend` image. In order for minikube to be able to use the image, we can set our docker client to point to the minikube docker host. To do this, run the following command: |
| 30 | + |
| 31 | +``` |
| 32 | +eval $(minikube docker-env) |
| 33 | +``` |
| 34 | + |
| 35 | +`$(minikube docker-env)` results in the following output: |
| 36 | + |
| 37 | +```bash |
| 38 | +export DOCKER_TLS_VERIFY="1" |
| 39 | +export DOCKER_HOST="tcp://192.168.99.100:2376" |
| 40 | +export DOCKER_CERT_PATH="/home/brian/.minikube/certs" |
| 41 | +# Run this command to configure your shell: |
| 42 | +# eval $(minikube docker-env) |
| 43 | +``` |
| 44 | + |
| 45 | +Notice that the `DOCKER_HOST` is pointing to the minikube VM on docker's default port `2376`. |
| 46 | + |
| 47 | +With these environment variables set, let's build the Django container image with the following command: |
| 48 | + |
| 49 | +```bash |
| 50 | +docker build -t backend:<TAG> -f backend/scripts/dev/Dockerfile backend/ |
| 51 | +``` |
| 52 | + |
| 53 | +**`deployment.yml`** |
| 54 | + |
| 55 | +```yml |
| 56 | +apiVersion: apps/v1 |
| 57 | +kind: Deployment |
| 58 | +metadata: |
| 59 | + name: django-backend |
| 60 | + labels: |
| 61 | + app: django-backend |
| 62 | +spec: |
| 63 | + replicas: 1 |
| 64 | + selector: |
| 65 | + matchLabels: |
| 66 | + app: django-backend |
| 67 | + template: |
| 68 | + metadata: |
| 69 | + labels: |
| 70 | + app: django-backend |
| 71 | + spec: |
| 72 | + containers: |
| 73 | + - name: django-backend-container |
| 74 | + image: localhost:5000/backend |
| 75 | + command: ["./manage.py", "runserver"] |
| 76 | + ports: |
| 77 | + - containerPort: 8000 |
| 78 | +``` |
| 79 | +
|
| 80 | +Let's send this file to Kubernete API server with the following command: |
| 81 | +
|
| 82 | +``` |
| 83 | +kubectl apply -f kubernetes/django/deployment.yml |
| 84 | +``` |
| 85 | + |
| 86 | +Your pod for the deployment should be starting. Inspect the pods with `k get pods`. If there is an error with container startup, you might see something like this: |
| 87 | + |
| 88 | +``` |
| 89 | +k get pods |
| 90 | +NAME READY STATUS RESTARTS AGE |
| 91 | +django-backend-dd798db99-hkv2p 0/1 Error 0 3s |
| 92 | +``` |
| 93 | + |
| 94 | +If this is the case, inspect the logs of the container with the following command: |
| 95 | + |
| 96 | +I have intentionally cause the container to fail by not providing a `SECRET_KEY` environment variable (this is something that Django needs in order to start). |
| 97 | + |
| 98 | +Let's inspect the container logs to confirm this: |
| 99 | + |
| 100 | +```bash |
| 101 | +k logs django-backend-dd798db99-hkv2p |
| 102 | +Traceback (most recent call last): |
| 103 | + File "./manage.py", line 16, in <module> |
| 104 | + execute_from_command_line(sys.argv) |
| 105 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line |
| 106 | + utility.execute() |
| 107 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, in execute |
| 108 | + self.fetch_command(subcommand).run_from_argv(self.argv) |
| 109 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv |
| 110 | + self.execute(*args, **cmd_options) |
| 111 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/runserver.py", line 60, in execute |
| 112 | + super().execute(*args, **options) |
| 113 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute |
| 114 | + output = self.handle(*args, **options) |
| 115 | + File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/runserver.py", line 67, in handle |
| 116 | + if not settings.DEBUG and not settings.ALLOWED_HOSTS: |
| 117 | + File "/usr/local/lib/python3.7/site-packages/django/conf/__init__.py", line 79, in __getattr__ |
| 118 | + self._setup(name) |
| 119 | + File "/usr/local/lib/python3.7/site-packages/django/conf/__init__.py", line 66, in _setup |
| 120 | + self._wrapped = Settings(settings_module) |
| 121 | + File "/usr/local/lib/python3.7/site-packages/django/conf/__init__.py", line 176, in __init__ |
| 122 | + raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.") |
| 123 | +django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty. |
| 124 | +``` |
| 125 | +
|
| 126 | +We could either provide a fallback value in the Django settings (which would require rebuilding the image), or we could add an environment variable to the container definition in the Pod `spec` in `deployment.yml`: |
| 127 | +
|
| 128 | +```yml |
| 129 | + spec: |
| 130 | + containers: |
| 131 | + - name: backend |
| 132 | + imagePullPolicy: IfNotPresent |
| 133 | + image: backend:latest |
| 134 | + command: ["./manage.py", "runserver"] |
| 135 | + ports: |
| 136 | + - containerPort: 8000 |
| 137 | + env: |
| 138 | + - name: SECRET_KEY |
| 139 | + value: "my-secret-key" |
| 140 | +``` |
| 141 | +
|
| 142 | +This should work, but we will still see errors in logs because we Django will attempt to establish a connection with the Postgres database which we will be setting up next. |
| 143 | +
|
| 144 | +We can hit our public facing `hello-world` endpoint which should serve as a nice health check for the Django container. |
| 145 | +
|
| 146 | +Let's test this endpoint with `curl`. We haven't set up a Kubernetes `Service` yet, so we will have to curl the Django application from within the cluster. We can do this with: |
| 147 | +
|
| 148 | +``` |
| 149 | +k exec django-backend-757b5944d8-htssm -- curl -s http://172.17.0.5/api/hello-world/ |
| 150 | +``` |
| 151 | +
|
| 152 | +This gives us: |
| 153 | +
|
| 154 | +``` |
| 155 | +command terminated with exit code 7 |
| 156 | +``` |
| 157 | +
|
| 158 | +Our container has started, but the database connection has prevented the Django process from starting. In our Pod logs, we can see that no request have been received and the Django application is not listening on `0.0.0.0:8000`. |
| 159 | +
|
| 160 | +Let's come back to this once we have set up our Postgres database. |
| 161 | +
|
| 162 | +## Postgres |
| 163 | +
|
| 164 | +First, we create a `PersistentVolume` resource: |
| 165 | +
|
| 166 | +**`kubernetes/postgres/volume.yml`** |
| 167 | +
|
| 168 | +```yml |
| 169 | +kind: PersistentVolume |
| 170 | +apiVersion: v1 |
| 171 | +metadata: |
| 172 | + name: postgres-pv |
| 173 | + labels: |
| 174 | + type: local |
| 175 | +spec: |
| 176 | + storageClassName: manual |
| 177 | + capacity: |
| 178 | + storage: 2Gi |
| 179 | + accessModes: |
| 180 | + - ReadWriteOnce |
| 181 | + hostPath: |
| 182 | + path: /data/postgres-pv |
| 183 | +``` |
| 184 | +
|
| 185 | +::: warning storageClassName |
| 186 | +Clicking on the `storageClassName` gave a 404 error |
| 187 | +::: |
| 188 | +
|
| 189 | +::: warning Authentication failure |
| 190 | +Message with postgres authentication failure |
| 191 | +::: |
| 192 | +
|
| 193 | +After changing the `postgres` user password, the migrations are able to run successfully: |
| 194 | +
|
| 195 | +```bash |
| 196 | +k exec django-784d668c8b-9gbf7 -it -- ./manage.py migrat |
| 197 | +e |
| 198 | +loading minikube settings... |
| 199 | +Operations to perform: |
| 200 | + Apply all migrations: accounts, admin, auth, contenttypes, sessions, social_django |
| 201 | +Running migrations: |
| 202 | + Applying contenttypes.0001_initial... OK |
| 203 | + Applying contenttypes.0002_remove_content_type_name... OK |
| 204 | + Applying auth.0001_initial... OK |
| 205 | + Applying auth.0002_alter_permission_name_max_length... OK |
| 206 | + Applying auth.0003_alter_user_email_max_length... OK |
| 207 | + Applying auth.0004_alter_user_username_opts... OK |
| 208 | + Applying auth.0005_alter_user_last_login_null... OK |
| 209 | + Applying auth.0006_require_contenttypes_0002... OK |
| 210 | + Applying auth.0007_alter_validators_add_error_messages... OK |
| 211 | + Applying auth.0008_alter_user_username_max_length... OK |
| 212 | + Applying auth.0009_alter_user_last_name_max_length... OK |
| 213 | + Applying auth.0010_alter_group_name_max_length... OK |
| 214 | + Applying auth.0011_update_proxy_permissions... OK |
| 215 | + Applying accounts.0001_initial... OK |
| 216 | + Applying admin.0001_initial... OK |
| 217 | + Applying admin.0002_logentry_remove_auto_add... OK |
| 218 | + Applying admin.0003_logentry_add_action_flag_choices... OK |
| 219 | + Applying sessions.0001_initial... OK |
| 220 | + Applying social_django.0001_initial... OK |
| 221 | + Applying social_django.0002_add_related_name... OK |
| 222 | + Applying social_django.0003_alter_email_max_length... OK |
| 223 | + Applying social_django.0004_auto_20160423_0400... OK |
| 224 | + Applying social_django.0005_auto_20160727_2333... OK |
| 225 | + Applying social_django.0006_partial... OK |
| 226 | + Applying social_django.0007_code_timestamp... OK |
| 227 | + Applying social_django.0008_partial_timestamp... OK |
| 228 | +``` |
| 229 | +
|
| 230 | +::: warning Try this again |
| 231 | +Try this again with a clean version of minikube and using the Secrets resource. |
| 232 | +::: |
| 233 | +
|
| 234 | +I initially started the postgres container with a password set by environment variable. This may have set data in the |
| 235 | +
|
| 236 | +::: tip kubectl cheatsheet from kubernetes documentation |
| 237 | +[https://kubernetes.io/docs/reference/kubectl/cheatsheet/#viewing-finding-resources](https://kubernetes.io/docs/reference/kubectl/cheatsheet/#viewing-finding-resources) |
| 238 | +::: |
| 239 | +
|
| 240 | +
|
| 241 | +Show the service in kubernetes: |
| 242 | +
|
| 243 | +``` |
| 244 | +minikube service kubernetes-django-service |
| 245 | +``` |
| 246 | +
|
| 247 | +We have two options for how to run the frontend application in Kubernetes. |
| 248 | +
|
| 249 | +1) Servce the static content from Django |
| 250 | +2) Serve the static content from nginx and use another service. |
| 251 | +
|
| 252 | +Serving the static content from Django was not working, so I'm building another deployment/service for frontend. For this to work, we need to tell Quasar about the address for the Django service. There are two ways to do this: |
| 253 | +
|
| 254 | +1) DNS |
| 255 | +2) Environment variables |
| 256 | +
|
| 257 | +
|
| 258 | +## Use environment variables to get service IP/Host |
| 259 | +
|
| 260 | +This won't be possible with out static front end site. |
| 261 | +
|
| 262 | +## Use DNS for services |
| 263 | +
|
| 264 | +DNS will be easier since we are building static assets outside of the context of Kubernetes and the environment variables that it injects at runtime. |
| 265 | +
|
| 266 | +``` |
| 267 | +/ # curl http://kubernetes-django-service:8000/api/ |
| 268 | +{"message": "Root"} |
| 269 | +``` |
| 270 | +
|
| 271 | +This works from inside of a pod, but we can't use this for our static site since `localhost` won't know how to resolve `kubernetes-django-service`. One solution for this is to get the `port:ip` of the backend service from minikube, and then use this value for the `baseUrl` in our frontend application: |
| 272 | +
|
| 273 | +**minikube/Dockerfile** |
| 274 | +
|
| 275 | +```Dockerfile |
| 276 | +# build stage |
| 277 | +FROM node:10-alpine as build-stage |
| 278 | +WORKDIR /app/ |
| 279 | +ENV DOMAIN_NAME 192.168.99.101:30958 <-- this is the nodePort for the backend service |
| 280 | +ENV HTTP_PROTOCOL http |
| 281 | +COPY quasar/package.json /app/ |
| 282 | +RUN npm cache verify |
| 283 | +RUN npm install -g @quasar/cli |
| 284 | +RUN npm install --progress=false |
| 285 | +COPY quasar /app/ |
| 286 | +RUN quasar build -m pwa |
| 287 | +
|
| 288 | +# minikube stage |
| 289 | +FROM nginx:1.13.12-alpine as minikube |
| 290 | +COPY nginx/minikube/minikube.conf /etc/nginx/nginx.conf |
| 291 | +COPY --from=build-stage /app/dist/pwa /dist/ |
| 292 | +EXPOSE 80 |
| 293 | +CMD ["nginx", "-g", "daemon off;"] |
| 294 | +``` |
| 295 | +
|
| 296 | +We need to set the `DOMAIN_NAME` environment variable to `kubernetes-django-service`, and also set the port, and we should be able to access the backend from frontend AJAX calls. |
| 297 | +
|
| 298 | +## Troubleshooting and Misc |
| 299 | +
|
| 300 | +https://stackoverflow.com/questions/55573426/virtualbox-is-configured-with-multiple-host-only-adapters-with-the-same-ip-whe |
| 301 | +
|
0 commit comments