From 621d2cbcde502005efb63759a48611b03fa853b3 Mon Sep 17 00:00:00 2001 From: Paul Harkink Date: Sat, 28 Feb 2026 15:24:42 +0100 Subject: [PATCH] =?UTF-8?q?feat(ex01):=20ArgoCD=20bootstrap=20=E2=80=94=20?= =?UTF-8?q?Vagrantfile,=20helm=20install,=20app-of-apps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Vagrantfile: Ubuntu 24.04, k3s (no traefik/servicelb), Helm, yq, host-only 192.168.56.10, pre-pulls key images - scripts/bootstrap.sh: installs ArgoCD via Helm, applies root app - apps/project.yaml: permissive AppProject for workshop - apps/root.yaml: App-of-Apps watching apps/ directory - apps/argocd.yaml: ArgoCD self-manages via Helm chart 7.7.11 - manifests/argocd/values.yaml: insecure mode, port-forward access --- .gitignore | 10 +++ Vagrantfile | 126 +++++++++++++++++++++++++++++++++++ apps/argocd.yaml | 36 ++++++++++ apps/project.yaml | 20 ++++++ apps/root.yaml | 20 ++++++ manifests/argocd/values.yaml | 48 +++++++++++++ scripts/bootstrap.sh | 65 ++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 .gitignore create mode 100644 Vagrantfile create mode 100644 apps/argocd.yaml create mode 100644 apps/project.yaml create mode 100644 apps/root.yaml create mode 100644 manifests/argocd/values.yaml create mode 100755 scripts/bootstrap.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8cc8b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# AI session / planning files — local only, not for participants +CLAUDE.md +sessions.md +roadmap.md + +# Vagrant +.vagrant/ + +# macOS +.DS_Store diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..7f3e987 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,126 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# ops-demo Vagrantfile +# Provisions Ubuntu 24.04 + k3s (no traefik, no servicelb) + Helm + Git +# Two network adapters: +# Adapter 1: NAT (internet access) +# Adapter 2: Host-only 192.168.56.x (MetalLB L2 — reachable from laptop) + +VAGRANTFILE_API_VERSION = "2" + +VM_NAME = "ops-demo" +VM_CPUS = 4 +VM_MEMORY = 8192 # 8 GB — ArgoCD + Tekton need headroom +HOST_ONLY_IP = "192.168.56.10" + +# k3s version — pin so the workshop is reproducible +K3S_VERSION = "v1.31.4+k3s1" + +$provision = <<-SHELL + set -euxo pipefail + + export DEBIAN_FRONTEND=noninteractive + + # ── 1. System packages ───────────────────────────────────────────────────── + apt-get update -qq + apt-get install -y -qq \ + curl git jq unzip bash-completion + + # ── 2. k3s ───────────────────────────────────────────────────────────────── + # Disable traefik and the built-in service-lb so MetalLB can take over. + # Bind to the host-only interface so ArgoCD LB IP is reachable from the host. + curl -sfL https://get.k3s.io | \ + INSTALL_K3S_VERSION="#{K3S_VERSION}" \ + K3S_KUBECONFIG_MODE="644" \ + sh -s - server \ + --disable=traefik \ + --disable=servicelb \ + --node-ip=#{HOST_ONLY_IP} \ + --advertise-address=#{HOST_ONLY_IP} + + # Wait for k3s to be ready + until kubectl get nodes 2>/dev/null | grep -q ' Ready'; do + echo "Waiting for k3s node to be ready..." + sleep 5 + done + echo "k3s is ready." + + # ── 3. kubeconfig for vagrant user ───────────────────────────────────────── + mkdir -p /home/vagrant/.kube + cp /etc/rancher/k3s/k3s.yaml /home/vagrant/.kube/config + # Point server to host-only IP so it works outside the VM too + sed -i "s|127.0.0.1|#{HOST_ONLY_IP}|g" /home/vagrant/.kube/config + chown -R vagrant:vagrant /home/vagrant/.kube + + # Also export KUBECONFIG in .bashrc + echo 'export KUBECONFIG=/home/vagrant/.kube/config' >> /home/vagrant/.bashrc + echo 'source <(kubectl completion bash)' >> /home/vagrant/.bashrc + echo 'alias k=kubectl' >> /home/vagrant/.bashrc + echo 'complete -o default -F __start_kubectl k' >> /home/vagrant/.bashrc + + # ── 4. Helm ──────────────────────────────────────────────────────────────── + HELM_VERSION="v3.16.4" + curl -sSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | \ + DESIRED_VERSION="${HELM_VERSION}" bash + echo 'source <(helm completion bash)' >> /home/vagrant/.bashrc + + # ── 5. yq (used by Tekton bump-image-tag task) ───────────────────────────── + YQ_VERSION="v4.44.3" + curl -sSL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ + -o /usr/local/bin/yq + chmod +x /usr/local/bin/yq + + # ── 6. Pre-pull key images (offline resilience) ──────────────────────────── + # k3s uses containerd; pull via ctr + export CONTAINERD_ADDRESS=/run/k3s/containerd/containerd.sock + export CONTAINERD_NAMESPACE=k8s.io + + images=( + "quay.io/argoproj/argocd:v2.13.3" + "ghcr.io/stefanprodan/podinfo:6.6.2" + "ghcr.io/stefanprodan/podinfo:6.7.0" + "quay.io/metallb/controller:v0.14.9" + "quay.io/metallb/speaker:v0.14.9" + "registry.k8s.io/ingress-nginx/controller:v1.12.0" + "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/controller:v0.65.1" + "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/webhook:v0.65.1" + "alpine/git:latest" + "mikefarah/yq:4.44.3" + ) + + for img in "${images[@]}"; do + echo "Pre-pulling: ${img}" + k3s ctr images pull "${img}" || echo "WARNING: failed to pull ${img} (will retry at runtime)" + done + + echo "" + echo "════════════════════════════════════════════════════════" + echo " VM provisioned successfully!" + echo " SSH: vagrant ssh" + echo " Next step: follow docs/vm-setup.md to verify, then" + echo " run scripts/bootstrap.sh to install ArgoCD" + echo "════════════════════════════════════════════════════════" +SHELL + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "bento/ubuntu-24.04" + config.vm.box_version = "~> 202502" # pin major release; allows patch updates + config.vm.hostname = VM_NAME + + # Adapter 2: host-only so MetalLB IPs are reachable from the laptop + config.vm.network "private_network", ip: HOST_ONLY_IP + + # Sync the repo into /vagrant (default) — participants work inside the VM + config.vm.synced_folder ".", "/vagrant", type: "virtualbox" + + config.vm.provider "virtualbox" do |vb| + vb.name = VM_NAME + vb.cpus = VM_CPUS + vb.memory = VM_MEMORY + # Needed for nested networking + vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] + end + + config.vm.provision "shell", inline: $provision, privileged: true +end diff --git a/apps/argocd.yaml b/apps/argocd.yaml new file mode 100644 index 0000000..3fc68b5 --- /dev/null +++ b/apps/argocd.yaml @@ -0,0 +1,36 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: argocd + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + project: workshop + source: + repoURL: https://argoproj.github.io/argo-helm + chart: argo-cd + targetRevision: "7.7.11" + helm: + valueFiles: + - $values/manifests/argocd/values.yaml + sources: + - repoURL: https://argoproj.github.io/argo-helm + chart: argo-cd + targetRevision: "7.7.11" + helm: + valueFiles: + - $values/manifests/argocd/values.yaml + - repoURL: https://github.com/innspire/ops-demo.git + targetRevision: HEAD + ref: values + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/apps/project.yaml b/apps/project.yaml new file mode 100644 index 0000000..ce10856 --- /dev/null +++ b/apps/project.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: workshop + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + description: Permissive project for workshop exercises + sourceRepos: + - "*" + destinations: + - namespace: "*" + server: https://kubernetes.default.svc + clusterResourceWhitelist: + - group: "*" + kind: "*" + namespaceResourceWhitelist: + - group: "*" + kind: "*" diff --git a/apps/root.yaml b/apps/root.yaml new file mode 100644 index 0000000..e79e0b7 --- /dev/null +++ b/apps/root.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: root + namespace: argocd +spec: + project: workshop + source: + repoURL: https://github.com/innspire/ops-demo.git + targetRevision: HEAD + path: apps + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/manifests/argocd/values.yaml b/manifests/argocd/values.yaml new file mode 100644 index 0000000..e3c5785 --- /dev/null +++ b/manifests/argocd/values.yaml @@ -0,0 +1,48 @@ +# ArgoCD Helm values for the workshop +# Phase 1: insecure mode + port-forward access only +# Phase 3: ingress added (see comment below) + +global: + nodeSelector: {} + +server: + # Run in insecure mode (no TLS on the server itself). + # TLS termination is handled by Ingress-Nginx in Exercise 03. + extraArgs: + - --insecure + + # ── Exercise 03: uncomment this block after Ingress-Nginx is deployed ────── + # ingress: + # enabled: true + # ingressClassName: nginx + # hostname: argocd.192.168.56.200.nip.io + # annotations: + # nginx.ingress.kubernetes.io/ssl-passthrough: "false" + # nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + # ────────────────────────────────────────────────────────────────────────── + +configs: + params: + server.insecure: true + + cm: + # Allow the root app to manage itself + application.resourceTrackingMethod: annotation + +repoServer: + resources: + requests: + cpu: 100m + memory: 256Mi + +applicationSet: + resources: + requests: + cpu: 50m + memory: 128Mi + +notifications: + enabled: false + +dex: + enabled: false diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..9f63af7 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# bootstrap.sh — Install ArgoCD via Helm and apply the root App-of-Apps +# Run this once inside the VM after `vagrant up`. +# +# Usage: +# cd /vagrant +# ./scripts/bootstrap.sh +# +# What it does: +# 1. Creates the argocd namespace +# 2. Installs ArgoCD via Helm using manifests/argocd/values.yaml +# 3. Waits for ArgoCD server to be ready +# 4. Applies apps/root.yaml (App-of-Apps entry point) +# 5. Prints the initial admin password and a port-forward hint + +set -euo pipefail + +ARGOCD_NAMESPACE="argocd" +ARGOCD_CHART_VERSION="7.7.11" # ArgoCD chart 7.x → ArgoCD v2.13.x +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +echo "══════════════════════════════════════════════" +echo " ops-demo Bootstrap" +echo "══════════════════════════════════════════════" + +# ── 1. Namespace ────────────────────────────────────────────────────────────── +echo "→ Creating namespace: ${ARGOCD_NAMESPACE}" +kubectl create namespace "${ARGOCD_NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - + +# ── 2. Helm install ArgoCD ──────────────────────────────────────────────────── +echo "→ Adding Argo Helm repo" +helm repo add argo https://argoproj.github.io/argo-helm --force-update +helm repo update argo + +echo "→ Installing ArgoCD (chart ${ARGOCD_CHART_VERSION})" +helm upgrade --install argocd argo/argo-cd \ + --namespace "${ARGOCD_NAMESPACE}" \ + --version "${ARGOCD_CHART_VERSION}" \ + --values "${REPO_ROOT}/manifests/argocd/values.yaml" \ + --wait \ + --timeout 5m + +# ── 3. Apply root App-of-Apps ───────────────────────────────────────────────── +echo "→ Applying root App-of-Apps" +kubectl apply -f "${REPO_ROOT}/apps/project.yaml" +kubectl apply -f "${REPO_ROOT}/apps/root.yaml" + +# ── 4. Print admin password ─────────────────────────────────────────────────── +ARGOCD_PASSWORD=$(kubectl -n "${ARGOCD_NAMESPACE}" get secret argocd-initial-admin-secret \ + -o jsonpath="{.data.password}" | base64 -d) + +echo "" +echo "══════════════════════════════════════════════" +echo " Bootstrap complete!" +echo "" +echo " ArgoCD admin password: ${ARGOCD_PASSWORD}" +echo "" +echo " To open the ArgoCD UI, run in a new terminal:" +echo " kubectl port-forward svc/argocd-server -n argocd 8080:443" +echo " Then open: https://localhost:8080" +echo " Login: admin / ${ARGOCD_PASSWORD}" +echo "" +echo " After Exercise 03, ArgoCD will also be reachable at:" +echo " https://argocd.192.168.56.200.nip.io" +echo "══════════════════════════════════════════════"