Skip to content

Latest commit

 

History

History
397 lines (328 loc) · 14.8 KB

README.md

File metadata and controls

397 lines (328 loc) · 14.8 KB

Part VII: Dependency management

In this part we will take a look at dependency management. With Helm you can define dependencies which should be deployed together with your application. This can be really helpful when you have a bunch of applications which need to work together. Instead of creating a very big chart, which contains every application, you can try to split them up and create multiple charts.

Note: It's very hard to give you a clear threshold when to split up your charts. An indicator could be when the values.yaml gets very complex and too long.

Requirements

When you define dependencies you need to create a file called requirements.yaml. We will go through the process of setting up such a file, of course, with an example: Let's take a look back at our Ghost application. In the first part we ran our blog software with a SQLite database. Imagine you are now facing the problem that the traffic on your running blog instance is very high and the latency increases. After taking a look in your monitoring system, you notice that the database is the bottleneck (high I/O on the SQLite partition). You now have a few options like using faster storage or trying to add a cache. For our example we will try to switch the database from SQLite to MySQL. With MySQL you can use Master Slave replications which scale much better. So we need to modify our Ghost Chart. First we will create a requirements.yaml file:

touch requirements.yaml ghost/

And add the following lines to that file:

dependencies:
- name: mysql
  version: "1.4.0"
  repository: "https://kubernetes-charts.storage.googleapis.com/"

You need to know the name of the Chart you want to include as well as the version and the repository where to find it. For MySQL you can look up this info here.

To match the new requirements we will need to change some things. Mainly we need to tell Ghost to connect to our MySQL Database:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}

  labels:
    app: {{ template "ghost.fullname" . }}
    heritage: {{ .Release.Service }}
    release: {{ .Release.Name }}
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    component: "{{ .Values.statefulset.labels.component }}"

spec:
  serviceName: {{ .Release.Name }}
  replicas: {{ .Values.statefulset.replicas }}

  selector:
    matchLabels:
      app: {{ template "ghost.fullname" . }}
      component: {{ .Values.statefulset.labels.component }}

  template:
    metadata:
      labels:
        app: {{ template "ghost.fullname" . }}
        heritage: {{ .Release.Service }}
        release: {{ .Release.Name }}
        chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
        component: {{ .Values.statefulset.labels.component }}

    spec:
      containers:
      - name: {{ .Values.statefulset.dockerImage }}

        image: "{{ .Values.statefulset.dockerImage }}:{{ .Values.statefulset.dockerTag }}"
        imagePullPolicy: "{{ .Values.global.imagePullPolicy }}"

        env:
        - name: "database__client"
          value: "{{ .Values.databaseClient }}"
        - name: "database__connection__host"
          value: "{{ .Release.Name }}-mysql"
        - name: "database__connection__user"
          value: "{{ .Values.mysql.mysqlUser }}"
        - name: "database__connection__password"
          value: "{{ .Values.mysql.mysqlPassword }}"
        - name: "database__connection__database"
          value: "{{ .Values.mysql.mysqlDatabase }}"

        readinessProbe:
          httpGet:
            path: /
            port: {{ .Values.statefulset.ports.port }}
          initialDelaySeconds: {{ .Values.statefulset.readiness.initialDelaySeconds }}
          timeoutSeconds: {{ .Values.statefulset.readiness.timeoutSeconds }}

        livenessProbe:
          httpGet:
            path: /
            port: {{ .Values.statefulset.ports.port }}
          initialDelaySeconds: {{ .Values.statefulset.liveness.initialDelaySeconds }}
          periodSeconds: {{ .Values.statefulset.liveness.periodSeconds }}
          timeoutSeconds: {{ .Values.statefulset.liveness.timeoutSeconds }}

        resources:
          requests:
            cpu: {{ .Values.statefulset.resources.requests.cpu }}
            memory: {{ .Values.statefulset.resources.requests.mem }}
          limits:
            cpu: {{ .Values.statefulset.resources.limits.cpu }}
            memory: {{ .Values.statefulset.resources.limits.mem }}

        ports:
        - name: {{ .Values.statefulset.ports.name }}
          protocol: {{ .Values.statefulset.ports.protocol }}
          containerPort: {{ .Values.statefulset.ports.port }}

        volumeMounts:
        - mountPath: /var/lib/ghost/content
          name: content

  volumeClaimTemplates:
  - metadata:
      name: content
      namespace: {{ .Release.Namespace }}
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: standard
      resources:
        requests:
          storage: 500Mi

Note: You may wonder why we don't convert the Statefulset to a Deployment since we have a external database to persist our data. Ghost stores not all information into the database. Images and other uploads will remain on disk. In our case in the PersistentVolume of the Statefulset.

We added a bunch of environment variables to configure the database connection for Ghost (see env section). All available env vars are listed here.

We also need to add some things to our values.yaml:

# This is a YAML-formatted file.
# Declare variables to be passed into your template.

# Global var section
global:
  imagePullPolicy: "Always"                         # The Kubernetes image pull policy
  domain: "example"                                 # The domain to use

# Ghost section

# general section
ingressUrl: "myawesomeblog"                       # The Ingress URL to use
databaseClient: "mysql"                           # The database to use

# statefulset section
statefulset:
  replicas: 1                                     # The number of replicas when deployed
  dockerImage: "ghost"                            # The docker image to use
  dockerTag: "1.24.8-alpine"                      # The docker tags to use

  # Label section
  labels:
    component: "frontend"                         # The component label to use

  # Resource section
  resources:
    requests:
      mem: "200Mi"                                # The initial amount of memory ghost requests
      cpu: "200m"                                 # The initial amount of cpu ghost requests
    limits:
      mem: "200Mi"                                # The maximum amount of memory ghost can consume before it will get killed
      cpu: "200m"                                 # The maximum amount of cpu ghost can consume

  # Readiness probe
  readiness:
    initialDelaySeconds: 120                      # The initial delay before the readiness probe starts
    timeoutSeconds: 5                             # The timeout for the readiness probe

  # Liveness probe
  liveness:
    initialDelaySeconds: 120                      # The initial delay after the readiness probe has finished
    timeoutSeconds: 5                             # The timeout for liveness probe
    periodSeconds: 10                             # The interval in which the liveness probe should be executed

  # Port section
  ports:
    name: web                                     # The name of the port
    protocol: TCP                                 # The protocol to use
    port: 2368                                    # The port number to use
    targetPort: 2368                              # The target port for the service object

# MySQL section
mysql:

  # General
  mysqlUser: "ghost"                                # The username to use
  mysqlPassword: "myAwesomeBlog"                    # The password for the user
  mysqlDatabase: "ghost"                            # The database to use

We need to fill the environment vars mentioned in the Deployment. The interesting part is located at the end of the file. There you see a new section called mysql. This is used to actually set the user, password and database in the mysql chart. Remember we included the mysql chart as a dependency under the name mysql. To find the right variables to override I recommend to take a look at the readme page of the stable repo section. This step can be really confusing since we override the values of another chart in our Ghost chart.

Note: I recommend to only override values in the top level chart and not in any subchart.

Note: I included all templates for this section in part-07/templates. When you face problems feel free to take a look at them.

Before we can deploy our chart with dependencies we need to fetch them. Helm also offers a command for this which is called dependency update.

helm dependency update ghost
Hang tight while we grab the latest from your chart repositories...
...Unable to get an update from the "local" chart repository (http://127.0.0.1:8879/charts):
        Get http://127.0.0.1:8879/charts/index.yaml: dial tcp 127.0.0.1:8879: connect: connection refused
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts
Downloading mysql from repo https://kubernetes-charts.storage.googleapis.com/
Deleting outdated charts

Helm will try to find the requirements.yaml and resolves all listed dependencies by downloading the corresponding charts. To verify what Helm really did, we can take a look into our charts directory:

ls -al ghost/
total 32
drwxr-xr-x  9 mll0005f  631410114   288 22 Jul 19:12 .
drwxr-xr-x  3 mll0005f  631410114    96 22 Jul 18:30 ..
-rw-r--r--  1 mll0005f  631410114   515 22 Jul 18:27 Chart.yaml
-rw-r--r--  1 mll0005f  631410114     0 22 Jul 18:27 README.md
drwxr-xr-x  3 mll0005f  631410114    96 22 Jul 19:12 charts
-rw-r--r--  1 mll0005f  631410114   235 22 Jul 19:12 requirements.lock
-rw-r--r--  1 mll0005f  631410114   113 22 Jul 18:33 requirements.yaml
drwxr-xr-x  7 mll0005f  631410114   224 22 Jul 19:08 templates
-rw-r--r--  1 mll0005f  631410114  2628 22 Jul 19:09 values.yaml

We also find a new file called requirements.lock which contains the following data:

dependencies:
- name: mysql
  repository: https://kubernetes-charts.storage.googleapis.com/
  version: 0.8.2
digest: sha256:8314e90806de6a8f2b6937679fdca7f13f172926d60e3c6838b0dd1f2c431c68
generated: 2018-07-22T19:12:20.36788332+02:00

With this lock file, Helm keeps track of pur dependencies. Besides some metadata it also contains a sha256 hash which can be used to verify the integrity of the downloaded package. there is also another new thing in our chart folder. A folder called charts:

ls -al ghost/charts
total 24
drwxr-xr-x  3 mll0005f  631410114    96 22 Jul 19:12 .
drwxr-xr-x  9 mll0005f  631410114   288 22 Jul 19:12 ..
-rw-r--r--  1 mll0005f  631410114  8278 22 Jul 19:12 mysql-0.8.2.tgz

Helm will there place all charts which are listed as a dependency. Now we can try to deploy our blog again. The command stays the same:

helm install --name blog --namespace=tools ./ghost
NAME:   blog
LAST DEPLOYED: Sun Jul 22 20:22:28 2018
NAMESPACE: tools
STATUS: DEPLOYED

RESOURCES:
==> v1/PersistentVolumeClaim
NAME        STATUS  VOLUME                                    CAPACITY  ACCESS MODES  STORAGECLASS  AGE
blog-mysql  Bound   pvc-30c309f0-8ddc-11e8-a620-08002779694e  8Gi       RWO           standard      0s

==> v1/Service
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)   AGE
blog-mysql  ClusterIP  10.102.155.20   <none>       3306/TCP  0s
blog        ClusterIP  10.103.242.207  <none>       2368/TCP  0s

==> v1beta1/Deployment
NAME        DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
blog-mysql  1        1        1           0          0s

==> v1/Deployment
blog  1  1  1  0  0s

==> v1beta1/Ingress
NAME  HOSTS                  ADDRESS  PORTS  AGE
blog  myawesomeblog.example  80       0s

==> v1/Pod(related)
NAME                         READY  STATUS             RESTARTS  AGE
blog-mysql-764569f779-wxnvk  0/1    Init:0/1           0         0s
blog-6fd6cd54b9-czsb9        0/1    ContainerCreating  0         0s

==> v1/Secret
NAME        TYPE    DATA  AGE
blog-mysql  Opaque  2     0s

==> v1/ConfigMap
NAME             DATA  AGE
blog-mysql-test  1     0s

When you take a look a the deployed resources you will see that the MySql DB was deployed along with Ghost. Let's see if Ghost can access the database by viewing the logs:

kubectl -n tools logs blog-6fd6cd54b9-czsb9 -f
[2018-07-22 17:27:39] INFO Creating table: posts
[2018-07-22 17:27:39] INFO Creating table: users
[2018-07-22 17:27:39] INFO Creating table: posts_authors
[2018-07-22 17:27:39] INFO Creating table: roles
[2018-07-22 17:27:39] INFO Creating table: roles_users
[2018-07-22 17:27:39] INFO Creating table: permissions
[2018-07-22 17:27:39] INFO Creating table: permissions_users
[2018-07-22 17:27:39] INFO Creating table: permissions_roles
[2018-07-22 17:27:39] INFO Creating table: permissions_apps
[2018-07-22 17:27:39] INFO Creating table: settings
[2018-07-22 17:27:39] INFO Creating table: tags
[2018-07-22 17:27:39] INFO Creating table: posts_tags
[2018-07-22 17:27:40] INFO Creating table: apps
[2018-07-22 17:27:40] INFO Creating table: app_settings
[2018-07-22 17:27:40] INFO Creating table: app_fields
[2018-07-22 17:27:40] INFO Creating table: clients
[2018-07-22 17:27:40] INFO Creating table: client_trusted_domains
[2018-07-22 17:27:40] INFO Creating table: accesstokens
[2018-07-22 17:27:40] INFO Creating table: refreshtokens
[2018-07-22 17:27:40] INFO Creating table: subscribers
[2018-07-22 17:27:40] INFO Creating table: invites
[2018-07-22 17:27:40] INFO Creating table: brute
[2018-07-22 17:27:40] INFO Creating table: webhooks
[2018-07-22 17:27:40] INFO Model: Tag
[2018-07-22 17:27:41] INFO Model: Client
[2018-07-22 17:27:41] INFO Model: Role
[2018-07-22 17:27:41] INFO Model: Permission
[2018-07-22 17:27:43] INFO Model: User
[2018-07-22 17:27:48] INFO Model: Post
[2018-07-22 17:27:50] INFO Relation: Role to Permission
[2018-07-22 17:27:52] INFO Relation: Post to Tag
[2018-07-22 17:27:52] INFO Relation: User to Role
[2018-07-22 17:28:13] INFO Finished database migration!
[2018-07-22 17:28:59] WARN Theme's file locales/en.json not found.
[2018-07-22 17:29:01] INFO Ghost is running in production...
[2018-07-22 17:29:01] INFO Your blog is now available on http://localhost:2368/
[2018-07-22 17:29:01] INFO Ctrl+C to shut down
[2018-07-22 17:29:01] INFO Ghost boot 47.103s

That looks very good. If you want, you can verify it by accessing the blog. To complete this part of the tutorial, we will delete the blog again.

helm del blog --purge
release "blog" deleted