feat(ex01): ArgoCD bootstrap — Vagrantfile, helm install, app-of-apps

- 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
This commit is contained in:
Paul Harkink 2026-02-28 15:24:42 +01:00
commit 621d2cbcde
7 changed files with 325 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# AI session / planning files — local only, not for participants
CLAUDE.md
sessions.md
roadmap.md
# Vagrant
.vagrant/
# macOS
.DS_Store

126
Vagrantfile vendored Normal file
View file

@ -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

36
apps/argocd.yaml Normal file
View file

@ -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

20
apps/project.yaml Normal file
View file

@ -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: "*"

20
apps/root.yaml Normal file
View file

@ -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

View file

@ -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

65
scripts/bootstrap.sh Executable file
View file

@ -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 "══════════════════════════════════════════════"