diff --git a/.gitea/workflows/build_push.yml b/.gitea/workflows/build_push.yml index d7f41b6..3480056 100644 --- a/.gitea/workflows/build_push.yml +++ b/.gitea/workflows/build_push.yml @@ -93,3 +93,83 @@ jobs: platforms: linux/amd64 tags: | git.aridgwayweb.com/armistace/beer-data:latest + + # The deploy job is separate from the build job on purpose. The + # `KUBEC_CONFIG_BUILDX` secret is cluster-admin-equivalent, and a build + # step that never talks to the cluster should not be able to read it - a + # compromised dependency in the build would otherwise have a direct path to + # the cluster. + deploy: + name: Deploy to the cluster + runs-on: ubuntu-latest + needs: build + if: gitea.ref == 'refs/heads/master' + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create Kubeconfig + run: | + mkdir -p $HOME/.kube + echo "${{ secrets.KUBEC_CONFIG_BUILDX }}" > $HOME/.kube/config + + - name: Install kubectl + run: | + apt-get update + apt-get install -y apt-transport-https ca-certificates curl gnupg + curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg + chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg + echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list + apt-get update + apt-get install -y kubectl + + - name: Deploy + run: | + # Apply the namespace and the non-secret manifests. The Secrets + # (beer-db, beer-app) are hand-provisioned and are NOT in the repo, + # so they are deliberately not in the apply set. + kubectl apply -f kube/beer_namespace.yaml + kubectl apply -f kube/beer-deployment.yaml + kubectl apply -f kube/beer-service.yaml + + # The image tag is `:latest`, which is immutable in the manifest, so + # `kubectl apply` alone does NOT roll the pods. Without the restart + # the deployment reports success while the old pods keep serving + # the old image. + kubectl -n beer rollout restart deployment/beer-deployment + kubectl -n beer rollout status deployment/beer-deployment --timeout=180s + + - name: Verify the new image is actually running + run: | + # CI green and pods Ready do NOT prove the new code is live. Compare + # the running digest against the registry's :latest. + RUNNING=$(kubectl -n beer get pod -l app=beer-data \ + -o jsonpath='{.items[0].status.containerStatuses[0].imageID}') + REGISTRY=$(curl -s -u "armistace:${{ secrets.REG_PASSWORD }}" -I \ + "https://git.aridgwayweb.com/v2/armistace/beer-data/manifests/latest" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + | grep -i docker-content-digest | awk '{print $2}' | tr -d '\r') + echo "running: $RUNNING" + echo "registry: $REGISTRY" + if [ "$RUNNING" != "$REGISTRY" ]; then + echo "ERROR: the running pod is not the image that was just pushed" + exit 1 + fi + + - name: Smoke test the deployed app + run: | + # The pods are behind a NodePort. Hit the service directly from + # inside the cluster. + for i in $(seq 1 12); do + if kubectl -n beer exec deploy/beer-deployment -- \ + python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5000/healthz').status==200 else 1)"; then + echo "healthz OK" + break + fi + echo "waiting for the app to come up ($i/12)" + sleep 5 + done + kubectl -n beer exec deploy/beer-deployment -- \ + python -c "import urllib.request,sys; r=urllib.request.urlopen('http://127.0.0.1:5000/readyz'); print('readyz:', r.read().decode()); sys.exit(0 if r.status==200 else 1)"