Feedback

Chat Icon

GitOps the Hard Way, with Argo CD

Build Real GitOps Pipelines From Empty Clusters to Automated Deploys

Stand Up the Lab: Two Servers, a Cluster, and an App to Deploy
28%

The Platform

In this section, we are going to create a Kubernetes cluster. You have multiple options to choose from. In this guide, we will use K3s, a lightweight, fully compliant Kubernetes distribution from Rancher. It ships as a single binary, installs in seconds, and is a good fit for a learning environment because it removes most of the operational overhead of a full Kubernetes install while behaving like the real thing. If your choice is different, you can skip this section and adapt the instructions to your platform.

Installing K3s

We will install K3s on the mycluster server. SSH into it first:

ssh -i $HOME/.ssh/learning root@

K3s provides an install script that detects your system, downloads the binary, and registers a systemd service. Run it, pinning the version so your cluster matches the one used in this guide:

# Export the public IP address of the mycluster server as an environment variable
MYCLUSTER_PUBLIC_IP=[PUBLIC_IP_ADDRESS_OF_YOUR_MYCLUSTER_SERVER]

# Export the version of K3s to install as an environment variable
export INSTALL_K3S_VERSION=v1.36.1+k3s1

curl -sfL https://get.k3s.io | \
  INSTALL_K3S_VERSION=$INSTALL_K3S_VERSION \
  sh -s - --node-external-ip $MYCLUSTER_PUBLIC_IP

This command does many things at once, so it is worth unpacking.

The install script reads the K3s version from the INSTALL_K3S_VERSION environment variable. Because we exported it, the script picks it up automatically. Pinning a version matters: without it the script installs whatever is latest, and a future K3s release could behave differently from what this guide expects.

Everything after sh -s - is passed as a flag to the K3s server itself, not to the install script. The --node-external-ip flag tells K3s the public address of this node. By default, K3s records only the node's internal (VPC) IP in the Kubernetes node object and leaves the ExternalIP field empty. Setting this flag now means kubectl get nodes will report the public IP later, which we rely on when building the URL to reach the Argo CD Web UI through a NodePort service. Skipping it here would force you to reinstall K3s later to add it.

When the script finishes, K3s is already running as a systemd service and a single-node cluster is up.

Making the API server reachable

By default, K3s generates the API server's TLS certificate for a fixed set of addresses: 127.0.0.1, the node's internal IP, and a few cluster-internal addresses. Any client connecting through an address that is not on that certificate will be rejected with a TLS verification error, even if the network connection itself succeeds.

This matters because the gitops server, where we will run kubectl and Argo CD, is a separate machine. It connects to mycluster over the network, so the address it uses must be present on the certificate. We add the addresses we need with the tls-san (Subject Alternative Name) option in the K3s config file.

We add two addresses. The private IP lets the gitops server reach the cluster over the VPC if both servers share one, which keeps that traffic off the public internet. The public IP is the fallback that works regardless of VPC layout. Including both means the certificate is valid no matter which route you connect through.

Export both addresses as environment variables:

# The private (VPC) IP of the mycluster server
MYCLUSTER_PRIVATE_IP=[PRIVATE_IP_OF_MYCLUSTER]

# The public IP of the mycluster server
MYCLUSTER_PUBLIC_IP=[PUBLIC_IP_OF_MYCLUSTER]

Create the K3s config file with both addresses listed under tls-san:

cat << EOF > /etc/rancher/k3s/config.yaml
tls-san:
  - "$MYCLUSTER_PRIVATE_IP"
  - "$MYCLUSTER_PUBLIC_IP"
EOF

K3s reads /etc/rancher/k3s/config.yaml at startup, but the certificate was already generated during install with the old set of addresses. Restarting the service forces K3s to regenerate the API server certificate, this time including the SANs we just added:

systemctl restart k3s

(i) If you ever change the tls-san list later, you must restart K3s again for the certificate to be reissued. K3s does not pick up the change on its own.

Getting the kubeconfig

K3s writes a ready-to-use kubeconfig to /etc/rancher/k3s/k3s.yaml

GitOps the Hard Way, with Argo CD

Build Real GitOps Pipelines From Empty Clusters to Automated Deploys

Enroll now to unlock all content and receive all future updates for free.