Deploying Django Application with PostgreSQL to Kubernetes Google Cloud cluster

5/2/2018

I am having trouble trying to deploy my Django Application and PostgreSQL database to Kubernetes Google Cloud cluster that I've already configured.

I have successfully created Docker containers for my Django Application and PostgreSQL database. Here is what my docker-compose.yml file looks like:

version: '3'

services:
  db:
    image: postgres
    environment:
      - POSTGRES_USER=stefan_radonjic
      - POSTGRES_PASSWORD=cepajecar995
      - POSTGRES_DB=agent_technologies_db
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000 --settings=agents.config.docker-settings
    volumes: 
      - .:/agent-technologies
    ports: 
      - "8000:8000"
    links:
      - db
    depends_on:
      - db

I have already build the images, and tried sudo docker-compose up command, and the application works perfectly fine.

After successfully dockerizing Django Application and PostgreSQL, I have tried to configure Deployment / Service YML files required by Kubernetes, but I am having trouble doing so. For example:

deployment-definition.yml - File for deploying Django application:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: agent-technologies-deployment
      labels:
        app: agent-technologies
        tier: backend
    spec:
      template:
        metadata:
          name: agent-technologies-pod
          labels:
            app: agent-technologies
            tier: backend
        spec:
          containers:
            - name: 
              image:
              ports:
                - containerPort: 8000
        replicas:
        selector:
          matchLabels:
            tier: backend

Inside container list of dictionaries, I know that my container name should be web, but I am not sure where the image of that container is located so I do not know what should i specify as container image.

Another problem lies in postgres/deployment-definition.yml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres-container
  template:
    metadata:
      labels:
        app: postgres-container
        tier: backend
    spec:
      containers:
        - name: postgres-container
          image: postgres:9.6.6
          env:
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: postgres-credentials
                  key: user

            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-credentials
                  key: password

            - name: POSTGRES_DB
              value: agent_technologies_db
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: postgres-volume-mount
              mountPath: /var/lib/postgresql/data

      volumes:
        - name: postgres-volume-mount
          persistentVolumeClaim:
            claimName: postgres-pvc

I do not understand what volumeMounts and volumes are for, and if i even specified them correctly.

Here is my secret-definition.yml file:

apiVersion: v1
kind: Secret
metadata:
  name: postgres-credentials
type: Opaque
data:
  user: stefan_radonjic
  passowrd: cepajecar995

My postgres/service-definition.yml file:

apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  selector:
    app: postgres-container
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432

My postgres/volume-definition.yml file:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
  labels:
    type: local
spec:
  capacity:
    storage: 2Gi
  storageClassName: standard
  accessModes:
    - ReadWriteMany
  hostPath:
    path: /data/postgres-pv

And my postgres/volume-claim-definitono.yml file:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
  labels:
    type: local
spec:
  capacity:
    storage: 2Gi
  storageClassName: standard
  accessModes:
    - ReadWriteMany
  hostPath:
    path: /data/postgres-pv

Last but not least, my service-definition.yml file - for Django application

apiVersion: v1
kind: Service
metadata:
  name: agent-technologies-service
spec:
  selector:
    app: agent-technologies
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 8000
  type: NodePort

Besides the questions I have already asked above, I also want to ask am I doing this right? If not, what can I do to fix this.

-- Stefan Radonjic
django
docker
kubernetes
postgresql

1 Answer

5/3/2018

Inside container list of dictionaries, I know that my container name should be web, but I am not sure where the image of that container is located so I do not know what should i specify as container image.

  • Name for container is local to the pod (you can have several containers sharing same pod). Container name (web in your case) is for your files given under deployment:

    # setting name of first container within pod to web
    spec:
      containers:
        - name: web
  • Image for container has to be in some available docker container registry. There are multiple options from hosting own docker registry to use publicly available ones. In any case you have to be able to push in your build phase to that docker container registry (be it amazon ECR, Docker, Gitlab, self hosted...) and to pull from that registry from within kubernetes (security settings, pull secrets etc...). In your docker-compose file you use two containers. For db you use public postgres image, and for web you use build command and image is stored to local docker registry on that host only (you have to push it to public repository for k8s to be able to pull from it during deployment).

I do not understand what volumeMounts and volumes are for, and if i even specified them correctly.

  • In a nutshell, volumes are for attaching volumes to containers. Depending on your use case and decided architecture there are several approaches to volumes, but all in all they boil down to ephemeral, constant and persistent. Ephemeral will be lost on container termination or restart, constant (such as from configMaps) are used for passing configuration files to containers and persistent are most interesting for stateful applications (databases among other things). You can specify volumes in several ways, all volume have to have name (to be referenced by volumeMount) and either direct volume specification or volume claim specification (latter is advised for persistent volume since you can benefit from automatic provisioning that way).
  • VolumeMounts are for defining where on container file system predefined volume should be mounted. They reference volume to be mounted by name, provide mount point on container filesystem by mountPath and can have subpaths to specific files in some cases.
  • In your example you tied persistent volume claim obtained volume to data path of postgres (/var/lib/postgresql/data). Althought you use storage class that you didn't specify, interesting part is that your Persistent volume is defined as localpath on host. That means that on each node you have this database pod started you will end up pointing /var/lib/postgresql/data of that pod's db container to /data/postgres-pv on that specific node. This opens up you to following issue: say you have 3 nodes (A, B and C) and your database pod is started on A, uses A's /data/postgres-pv folder as own /var/lib/postrgresql/data. And then you restart it, it gets terminated and rescheduled to node B. All of the sudden, it uses B's /data/postgres-pv local folder (empty) and you end up with empty database. If you use host's local filesystem for persisntence you need to tie such pods with node (or better yet with affinity) selectors. It is advisable for performance reasons to run database volumes of local filesystem, but hose pods lose ability to be rescheduled easily. Another approach is to have some truly persistent volume that can be mounted independently of node (Amazon EBS for example) and they require different PVC (or provisioner to be used).

Besides the questions I have already asked above, I also want to ask am I doing this right? If not, what can I do to fix this.

  • As stated above, define storage class and either lock db pod to specific node or apply some kind of dynamic provisioning so volume will follow pod's placement on nods.
  • Oppiniated preference: don't place everything in default namespace, use separate namespace for handling k8s manifests, later on it is much harder to move everything, and harder to accidentally delete wrong thingie...
  • Also personal preference: database is stateful application and as such it is advised to use statefulset instead of deployment.
  • There are tools to help you out when you start from docker-compose files and want to convert to kubernetes manifests, worth checking.
  • Documentation on kubernetes is a bit outdated but quite good and you can have some nice read on volumes and volumeClaims there, there is also active slack channel.
  • Oh, and mock user/pass when posting files here, we know now about cepa...
  • Lastly, you are doing great job!
-- Const
Source: StackOverflow