Post 2: Setting up a workload cluster, and deploying a blog

Post 2: Setting up a workload cluster, and deploying a blog

Tanzu creates a Management Cluster, which is used to deploy one or more Workload Clusters which will actually run our applications.

To create a workload cluster follow the TCE docs. Make sure your kubectl config is set to the workload cluster (you can check this using kubectl config current-context ).

Installing kube-vip

Kubernetes runs workloads inside of pods. In order for those to be reachable outside of Kubernetes, they need to be exposed to the outside world using a service. Services come in 3 flavors: ClusterIP (the default, which only exposes a pod on the k8s network), NodePort (which exposes pods on a single IP using a range of ports), and LoadBalancer (which chooses from a range of IPs to assign to a pod). TCE does not come with a LoadBalancer implementation, allowing the user to choose one to use. We will be installing kube-vip (MetalLB is another choice).

To install kube-vip, we will be using a Carvel package created by fellow VMware employee Scott Rosenberg. You can follow Steps 1-5 in the excellent blog post by another VMware employee, William Lam.

If you want to be sure that your installation was successful, you could continue following William's tutorial and install the yelb demo application. Once you are done, you can uninstall yelb using kubectl delete all -n yelb (deletes everything in the yelb namepace) and kubectl delete ns yelb (deletes the namespace itself).

Hosting a Blog

As a part of the jumpbox installation helm was installed. Like Carvel, helm is a package manager for Kubernetes, which allows packages to be defined using charts, which can be installed with the helm install command. Charts also define a set of values, variables which can be set by the user to customize the install.

In order to install Ghost we first need to add Bitnami's chart repository to helm.

helm repo add bitnami https://charts.bitnami.com/bitnami

We can then install ghost with the following command:

helm install ghost bitnami/ghost
Info: The general helm install syntax is as follows: helm install name-of-release location/of/chart. Releases are helm's name for an instance of a chart. In our case we named the release "ghost".

Running kubectl get all you can get some of the kubernetes resources created by Ghost (note that unlike the name suggests "get all" does not show every Kubernetes resource, and excludes many e.g. ingresses and persistent volumes).

You can see that service/ghost is of type LoadBalancer has an external IP. This IP is assigned to it by kube-vip out of the pool of IPs specified in the kube-vip values file.

kubectl get all         
NAME                READY   STATUS    RESTARTS   AGE
pod/ghost-mysql-0   1/1     Running   0          4m50s

NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
service/ghost                  LoadBalancer   100.71.156.29   192.168.30.240   80:30201/TCP   4m51s
service/ghost-mysql            ClusterIP      100.70.89.188   <none>           3306/TCP       4m51s
service/ghost-mysql-headless   ClusterIP      None            <none>           3306/TCP       4m51s
service/kubernetes             ClusterIP      100.64.0.1      <none>           443/TCP        5h32m

NAME                           READY   AGE
statefulset.apps/ghost-mysql   1/1     4m51s

The eagle-eyed among you may notice the only pod is one for the database. That can't be right—where's the actual website?

As a part of the installation we needed to specify the hostname of the website. The problem is we don't know the IP address the Load Balancer will get until after the deployment.

No problem. We can simply get the address and other info, and then redeploy.

export APP_HOST=$(kubectl get svc --namespace default ghost --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
export GHOST_PASSWORD=$(kubectl get secret --namespace "default" ghost -o jsonpath="{.data.ghost-password}" | base64 -d)
export MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace "default" ghost-mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d)
export MYSQL_PASSWORD=$(kubectl get secret --namespace "default" ghost-mysql -o jsonpath="{.data.mysql-password}" | base64 -d)
helm upgrade --namespace default ghost bitnami/ghost --set service.type=LoadBalancer,ghostHost=$APP_HOST,ghostPassword=$GHOST_PASSWORD,mysql.auth.rootPassword=$MYSQL_ROOT_PASSWORD,mysql.auth.password=$MYSQL_PASSWORD

Now when we run kubectl get all we get the following output:

kubectl get all         
NAME                         READY   STATUS              RESTARTS   AGE
pod/ghost-75dd96d9f9-gc7dv   0/1     ContainerCreating   0          22s
pod/ghost-mysql-0            1/1     Running             0          5m35s

NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
service/ghost                  LoadBalancer   100.71.156.29   192.168.30.240   80:30201/TCP   5m36s
service/ghost-mysql            ClusterIP      100.70.89.188   <none>           3306/TCP       5m36s
service/ghost-mysql-headless   ClusterIP      None            <none>           3306/TCP       5m36s
service/kubernetes             ClusterIP      100.64.0.1      <none>           443/TCP        5h33m

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ghost   0/1     1            0           22s

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/ghost-75dd96d9f9   1         1         0       22s

NAME                           READY   AGE
statefulset.apps/ghost-mysql   1/1     5m35s
    

Once the ghost pod is created and ready you can go to the LoadBalancer external IP in your web browser, and you should see the ghost homepage.

In our next post we'll discuss exposing the blog to the internet, including support for HTTPS.