For the very first post of this blog, we will see together how I installed Ghost.
Source files are available here.
Ghost is a decoupled content management system allowing great flexibility.
Because its core is composed of a REST and a GraphQL API, we can plug in any technology to retrieve the publications and display them. By default, a turnkey interface is offered but nothing prevents you from using an Angular, React or Blazor frontend.
We're going to deploy it in Azure using Ghost's vanilla flavor. Follow me!
I'm going to disregard the creation of the environment to not expand too much, but you must have a Kubernetes server in hands.
1 - Creating the namespace
This will allow you to isolate resources to facilitate management and cleaning.
kubectl create namespace blog
2 - Creating a volume for permanent storage
As Docker containers are volatile, the information contained in them will not be kept following a restart unless you associate a volume with them. We will let Kubernetes auto create an Azure File Share for our blog with a PersistentVolumeClaim.
#File : blog-persistent-volume-claim.yml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: blog-pvc spec: accessModes: - ReadWriteMany storageClassName: azurefile resources: requests: storage: 50Gi
kubectl apply -f .\blog-persistent-volume-claim.yml --namespace=blog
3 - Creating a deployment
The deployment defines the specifications to be achieved for our Pods. Pods allow multiple containers to share the same context. Once deployed, a controller will monitor the current state of the resources to reach the desired specification. For example, if two Pods are requested and one is deleted, then a new Pod will be automatically be created.
#File : blog-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: blog namespace: blog labels: app: blog release: 3.38.2 spec: replicas: 1 selector: matchLabels: app: blog release: 3.38.2 template: metadata: labels: app: blog release: 3.38.2 spec: volumes: - name: blog-content persistentVolumeClaim: claimName: blog-pvc containers: - name: blog image: ghost:3.38.2-alpine env: - name: url value: <domain-name> volumeMounts: - name: blog-content mountPath: /var/lib/ghost/content resources: limits: cpu: "1" memory: 256Mi requests: cpu: 100m memory: 64Mi ports: - name: http containerPort: 2368 protocol: TCP restartPolicy: Always
kubectl apply -f .\blog-deployment.yml --namespace=blog
4 - Validation of deployment
Following a deployment, it is good practice to always check whether the operation went well.
kubectl get events --namespace=blog kubectl get pods --namespace=blog
You should have a container ready.
NAME READY STATUS RESTARTS AGE blog-679b759f94-kl87h 1/1 Pending 0 22s
5 - Creation of a service
We will now expose the Pods to an IP address internal to the cluster.
#File : blog-service.yml apiVersion: v1 kind: Service metadata: name: blog namespace: blog spec: type: ClusterIP selector: app: blog ports: - protocol: TCP port: 80 targetPort: 2368
kubectl apply -f .\blog-service.yml --namespace=blog
6 - Creation of the certificate
We will be using Let's Encrypt, which allows you to generate trusted certificates for free.
You must first have created a certificate issuer called "letsencrypt-production". The easiest way to do this is to use cert-manager. Here is the procedure : https://cert-manager.io/docs/installation/kubernetes/
Create your certificate :
#File : blog-tls.yml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: blog-tls spec: secretName: blog-tls dnsNames: - <domain-name> acme: config: - http01: ingressClass: nginx domains: - <domain-name> issuerRef: name: letsencrypt-production kind: ClusterIssuer
kubectl apply -f .\blog-tls.yml --validate=false --namespace=blog
7 - Creating an IngressController
The gateway to your services in Kubernetes will be an Nginx controller.
The good thing about it is that all traffic coming from outside is encrypted thanks to the certificate automatically published by Let's encrypt. Then, inside the cluster, we reduce the complexity of the services by only using the HTTP protocol.
#File : blog-ingress.yml apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: blog namespace: blog annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-production nginx.ingress.kubernetes.io/rewrite-target: /$1 nginx.ingress.kubernetes.io/use-regex: "true" spec: tls: - hosts: - <domain-name> secretName: blog-tls rules: - host: <domain-name> http: paths: - path: /(.*) backend: serviceName: blog servicePort: 80
kubectl apply -f .\blog-ingress.yml --namespace=blog
8- Domain name routing
All you have to do now is associate your domain name with the ClusterIp.
To do this, you will need to create an @ record and a * record with your registrar.
To get the public IP of the cluster:
kubectl get ingress --namespace=blog
Some more links
I plan to show how to backup and restore the Azure File Share. Tell me what you would like to see next! ;)