Getting Started with GitOps on Leaseweb Kubernetes Using Argo CD

GitOps with Argo CD on Leaseweb Kubernetes | Part 1

In this article, we will go from a default Leaseweb managed Kubernetes cluster to a production-ready cluster capable of delivering a stable and secure environment for all deployments. The goal of this article is not to present the only, or even the best, approach to achieve a production-ready cluster. As is the norm in IT, there are multiple different ways of accomplishing things, and selecting the best or most appropriate depends on the goal, the org, the people involved, and a multitude of other factors. As such, the goal here is merely to offer an approach to be tweaked and adjusted to each specific implementation.

To start, a Kubernetes cluster is needed. Follow these pages to provision your Leaseweb cluster (https://kb.leaseweb.com/kb/managing-a-kubernetes-cluster/configure-a-kubernetes-cluster/) and get the kubeconfig file (https://kb.leaseweb.com/kb/kubernetes/getting-started-with-kubernetes-overview/).

It is recommended to have at least 4 worker nodes for high availability.

Note
If you have fewer than 4 worker nodes, use the non-high availability manifests both when installing Argo CD and on the repo in the kustomization.yaml file.

Argo CD

Install Argo CD

To install Argo CD, the upstream documentation can be followed and is available here (https://argo-cd.readthedocs.io/en/stable/)

First, create the namespace where Argo CD will be installed:

kubectl create namespace argocd

Then apply the installation manifests:


kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

Access Argo CD

To log in, the default admin password is needed. It is created during installation and saved in the argocd-initial-admin-secret. To get it from the cluster run:

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d ; echo

By default, Argo CD’s API server is not exposed outside the cluster. To get access, port forwarding needs to be configured:

kubectl port-forward svc/argocd-server -n argocd 8080:443

Now it is possible to log in with the username admin and the password retrieved in the previous step by navigating to http://localhost:8080.

 

While Argo CD is installed and ready to be used, it is not yet production ready.

Manage Argo CD using Argo CD

Since Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes, it makes sense to manage Argo CD following these same principles. To do that a git repository is needed. The creation and configuration of the repository is outside of the scope of this article.

To configure access to the repository, a secret needs to be created. Depending on the way the repository should be accessed, the secret needs to be different. To see the different examples refer to the official documentation here (https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#repositories).

In this article https will be used. Create a secret with this manifest (adjust the git repo url, password and username):


kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: private-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: <git repo url>
  password: <password>
  username: <username>
EOF

After the repository is created an ApplicationSet will be used to keep the cluster in sync with the code in the repo (adjust the git repo url):


kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: apps
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: <git repo url>
      revision: HEAD
      directories:
      - path: apps/*/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: <git repo url>
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path[1]}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - PruneLast=true
        - ServerSideApply=true
EOF

This ApplicationSet will create a namespace per directory under the apps directory on the repo and an application per directory under the specific namespace directory. For illustration:

Directory structure example:


.
├── apps
│   ├── argocd
│   │   └── argocd
│   ├── namespace1
│   │   ├── application1
│   │   ├── application2
│   │   ├── application...
│   ├── namespace2
│   │   ├── application3
│   │   ├── application4
│   │   ├── application...
│   ├── namespace...

To create a new application, simply create a new directory in the desired namespace containing the manifests as exemplified in this article.

After these bootstrap steps, it is time to start following GitOps. To start, create the argocd application.

To do that, on the repo, create the directories for the namespace and application – argocd/argocd – and then a kustomization file with the following contents:

apps/argocd/argocd/kustomization.yaml


apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: argocd

resources:
- base/applicationset.yaml
- https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

And a directory called base with the ApplicationSet manifest file (adjust the git repo url):

apps/argocd/argocd/base/applicationset.yaml


apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: apps
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: <git repo url>
      revision: HEAD
      directories:
      - path: apps/*/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: <git repo url>
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path[1]}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - PruneLast=true
        - ServerSideApply=true

This will make Argo CD declaratively manage itself.

Traefik ingress controller

At this stage, Argo CD is only accessible with port forwarding enabled. To have it accessible from the outside using a human-readable website name, traefik will be used as ingress. As stated in the introduction, an ingress controller is only one example of how to make Argo CD accessible, and traefik is only one example of an ingress controller. For other possibilities, check the official documentation (https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/). To cover the human-readable website name, a DNS name is needed. The DNS configuration is outside of the scope of this article.

To install it, create the new directories under apps for the namespace and application and add the following helm chart:

apps/traefik/traefik/Chart.yaml


apiVersion: v2
name: traefik
version: 0.1.0
dependencies:
- name: traefik
  version: "*"
  repository: https://traefik.github.io/charts

With traefik installed, Argo CD needs to be configured to make use of it. First, argocd-server should run with TLS disabled. To disable it, a kustomization patch will be used.

First create the patch:

apps/argocd/argocd/overlays/argocd-cmd-params-cm.yaml


apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
data:
  server.insecure: "true"

And then apply it by editing the apps/argocd/argocd/kustomization.yaml file:

apps/argocd/argocd/kustomization.yaml


apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: argocd

resources:
- base/applicationset.yaml
- https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

patches:
- path: overlays/argocd-cmd-params-cm.yaml

After these changes are applied to the cluster, argocd-server needs to be restarted:


kubectl rollout restart deploy argocd-server -n argocd

With TLS disabled, the ingress needs to be configured. In the apps/argocd/argocd/base directory, create the IngressRoute file (adjust the domain name):

apps/argocd/argocd/base/argo-cd-ui-ingress.yaml


apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: argocd-server
  namespace: argocd
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`argocd.<your domain name>`)
      priority: 10
      services:
        - name: argocd-server
          port: 80
    - kind: Rule
      match: Host(`argocd.<your domain name>`) && Header(`Content-Type`, `application/grpc`)
      priority: 11
      services:
        - name: argocd-server
          port: 80
          scheme: h2c
  tls:
    certResolver: default

And add it under the resources of the kustomization file:

apps/argocd/argocd/kustomization.yaml


apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: argocd

resources:
- base/applicationset.yaml
- base/argo-cd-ui-ingress.yaml
- https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

patches:
- path: overlays/argocd-cmd-params-cm.yaml

Make sure argocd.<your domain name> resolves to the traefik public ip address. You can retrieve it from the Kubernetes cluster:


k get svc traefik -n traefik --output jsonpath='{.status.loadBalancer.ingress[0].ip}' ; echo

Argo CD is now accessible at argocd.<your domain name>

cert-manager

At this stage, Argo CD is using a self signed certificate. To have a certificate accepted by most browsers, cert-manager with Let’s Encrypt will be used. Again this is just one of many ways of doing it. There are multiple certificate providers and different tools to implement certificates.

To install cert-manager, create the new directories under apps for the namespace and application, and a kustomization file containing the following:

apps/cert-manager/cert-manager/kustomization.yaml


apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml

With cert-manager installed, argocd needs to be configured to make use of it.

First create an issuer (adjust the email address):

apps/argocd/argocd/base/argo-cd-issuer.yaml


apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: argo-cd-issuer
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <your email address>
    privateKeySecretRef:
      name: letsencrypt
    solvers:
      - selector: {}
        http01:
          ingress:
            class: traefik

and a certificate (adjust the domain name):

apps/argocd/argocd/base/argo-cd-certificate.yaml


apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: argo-cd-cert
spec:
  secretName: argocd-secret
  issuerRef:
    name: argo-cd-issuer
    kind: Issuer
  commonName: argocd.<your domain name>
  dnsNames:
  - argocd.<your domain name>

then add them under the resources of the kustomization file:

apps/argocd/argocd/kustomization.yaml


apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: argocd

resources:
- base/applicationset.yaml
- base/argo-cd-issuer.yaml
- base/argo-cd-certificate.yaml
- base/argo-cd-ui-ingress.yaml
- https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

patches:
- path: overlays/argocd-cmd-params-cm.yaml

And finally, configure the IngressRoute to use the certificate-authority-data (adjust the domain name):

apps/argocd/argocd/base/argo-cd-ui-ingress.yaml


apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: argocd-server
  namespace: argocd
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`argocd.<your domain name>`)
      priority: 10
      services:
        - name: argocd-server
          port: 80
    - kind: Rule
      match: Host(`argocd.<your domain name>`) && Header(`Content-Type`, `application/grpc`)
      priority: 11
      services:
        - name: argocd-server
          port: 80
          scheme: h2c
  tls:
    certResolver: default
    secretName: argocd-secret

Now argocd.<your domain name> should have a valid Let’s Encrypt certificate.

Deploying an application

Now with Argo CD properly configured, it is possible to proceed to deploying an application. As a demo, a separate namespace – demo-ns – will be created with an application – demo-app – containing a deployment running nginx with an example webpage.

First, create a new directory under apps with the name demo-ns, for the namespace, and a new directory under demo-ns with the name demo-app, for the application, and add the following manifests:

apps/demo-ns/demo-app/app.yaml


apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-configmap
data:
  index.html: |
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Welcome</title>
      <style>
        body {
          font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
          text-align: center;
          margin: 0;
          padding: 0;
          background-color: #f5f5f5;
          color: #333333;
          height: 100vh;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
        }
        h1 {
          background-color: #ff8400;
          color: white;
          padding: 20px 0;
          width: 100%;
          text-align: center;
          margin: 0;
        }
        #img {
          max-width: 100%;
          height: auto;
          display: block;
          margin: 0 auto;
          box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        }
        #pod-info
        {
          background-color: #5088c7;
          color: #ffffff;
          padding: 20px;
          width: 100%;
          text-align: center;
        }
      </style>
    </head>
    <body>
      <h1>Welcome to Leaseweb Managed Kubernetes</h1>
      <img id="img" src="https://cdn.leaseweb.com/images/leaseweb-layers.png" alt="Leaseweb Logo">
      <div id="pod-info">
        <h3>Pod Info</h3>
        <div>{{ .Values.podInfo }}</div>
      </div>
    </body>
    </html>

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: nginx-config
          mountPath: /usr/share/nginx/html/index.html
          subPath: index.html
        env:
        - name: podInfo
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-configmap

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

To get the public IP of the service, run the command:

k get svc demo-service -n demo-ns --output jsonpath='{.status.loadBalancer.ingress[0].ip}' ; echo

And navigate to it in a web browser:

Conclusion

In this article, we showed how to go from a freshly provisioned Leaseweb kubernetes cluster to a cluster following the GitOps principles using Argo CD and deployed an example application.

Now it is possible to proceed with other configurations – for example RBAC or SSO user management. It is recommended that the same approach used in this article to configure Argo CD using patches and overlays be used. To learn more about other Argo CD configurations, visit the official Argo CD documentation – https://argo-cd.readthedocs.io/en/stable/operator-manual/

Another good section to read from the official documentation is the best practices – https://argo-cd.readthedocs.io/en/stable/user-guide/best_practices/

Throughout this article, we have examples of applications using kustomize, helm and plain manifests. Use whichever is more appropriate to your organization or mix and match according to your needs.

Caution
Throughout this article the versions used were always the latest. In production it is recommended to pin versions and upgrade them with care.

Leave a Reply

Your email address will not be published. Required fields are marked *