From 8f8181aeefac926296d60a650060b3c599706703 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:18:01 +0100 Subject: [PATCH 01/11] Fix formatting --- manual-remote-access.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manual-remote-access.md b/manual-remote-access.md index 2907ccc..c0f7262 100644 --- a/manual-remote-access.md +++ b/manual-remote-access.md @@ -138,8 +138,8 @@ $ curl -X GET /api - On the minikube host: 1. Create a service account, `ClusterRoleBinding`, and token - - We create a service account called remote-dev that we will authenticate as - - We then create a `ClusterRoleBinding` referencing the `cluster-admin` role (created by default which provides full access to everything in the cluster) and bind the remote-dev account we just created to it + - We create a service account called remote-dev that we will authenticate as + - We then create a `ClusterRoleBinding` referencing the `cluster-admin` role (created by default which provides full access to everything in the cluster) and bind the remote-dev account we just created to it ```shell ubuntu@kubectl-ghactions-test:~$ kubectl create serviceaccount remote-dev ubuntu@kubectl-ghactions-test:~$ kubectl create clusterrolebinding remote-dev-binding \ @@ -177,7 +177,7 @@ current-context: remote-context - Test access by making a request ```shell -$ KUBECONFIG=$(pwd)/kube-config ./kubectl get ns +$ KUBECONFIG=$(pwd)/kube-config kubectl get ns NAME STATUS AGE default Active 7m57s kube-node-lease Active 7m57s From 17062fd33d57a15974c4c9b7d294081bddf3320f Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:40:31 +0100 Subject: [PATCH 02/11] Add note on production-readiness (or lack thereof) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4591cf1..cb585a3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # github-actions-k8s +## A quick note + +- This repo is an **example** of how to do this, and for that it uses minikube. This repo does **not** create a production ready environment, it merely displays the concepts and methods for remotely managing a cluster with kubectl. + +## Get started + - Running `kubectl` against a K8s cluster using GitHub Actions - This example runs a minikube cluster inside a VM managed by incus, then runs a `kubectl apply` against that cluster from a Github Actions runner - A publicly accessibly IP address/domain is required From c13a914560b3a9bbebefdd458c4d02eb20a7337a Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:09:47 +0100 Subject: [PATCH 03/11] Steps for creating github secret #1 --- kubectl-gh-actions.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 kubectl-gh-actions.md diff --git a/kubectl-gh-actions.md b/kubectl-gh-actions.md new file mode 100644 index 0000000..3e90077 --- /dev/null +++ b/kubectl-gh-actions.md @@ -0,0 +1,14 @@ +- Assuming you followed [manual-remote-access.md](./manual-remote-access.md), you should now have remote access to your minikube cluster set up + +# Steps + +1. Make the JWT token accessible to the GitHub runner + + - Note that generated JWT tokens are relatively short-lived, but you can extend their validity by passing `--duration=` to `kubectl create token` + - e.g. `kubectl create-token remote-dev --duration=12h` for a token valid for 12 hours + - We probably don't want to use these in production, your kubernetes provider (e.g. EKS) may offer a better means of authentication + + - On the webpage for your repo: + - Settings -> Secrets and Variables -> Actions -> New Repository Secret + - Set the name to `JWT_AUTH_TOKEN` + - Set the value to the JWT token you generated From ac4e5c8d923ad28043e207445f7e229d56be0b33 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:44:11 +0100 Subject: [PATCH 04/11] Initial workflow test --- .github/workflows/kubectl.yml | 32 +++++++++++++++++ kubectl-gh-actions.md | 65 ++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/kubectl.yml diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml new file mode 100644 index 0000000..3798c2c --- /dev/null +++ b/.github/workflows/kubectl.yml @@ -0,0 +1,32 @@ +name: Run kubectl against remote cluster +on: + workflow_dispatch: + push: + branches: + - "1-github-runner-manages-remote" +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Install kubectl + run: | + mkdir $HOME/bin + curl -Lf 'https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl' -o $HOME/bin/kubectl + chmod +x $HOME/bin/kubectl + echo "$HOME/bin" >> $GITHUB_PATH + + - name: Check kubectl is available on PATH + run: kubectl version --client + + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set kubeconfig with kubectl + run: | + kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" + kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" + kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" + kubectl config use-context "remote-context" + + - name: Run kubectl command against remote API + run: kubectl get namespaces diff --git a/kubectl-gh-actions.md b/kubectl-gh-actions.md index 3e90077..aae6127 100644 --- a/kubectl-gh-actions.md +++ b/kubectl-gh-actions.md @@ -2,7 +2,7 @@ # Steps -1. Make the JWT token accessible to the GitHub runner +1. Add secrets for the server address and JWT token - Note that generated JWT tokens are relatively short-lived, but you can extend their validity by passing `--duration=` to `kubectl create token` - e.g. `kubectl create-token remote-dev --duration=12h` for a token valid for 12 hours @@ -12,3 +12,66 @@ - Settings -> Secrets and Variables -> Actions -> New Repository Secret - Set the name to `JWT_AUTH_TOKEN` - Set the value to the JWT token you generated + - Add another secret called `API_SERVER_ADDR` with the value of your public-facing API server address + +2. Access the secret in the action + + - Github actions can access repository secrets using the syntax `${{ secrets. }}` + - We'll create a step in our action that sets the correct kubeconfig + + ```yaml + # Other steps... # + + - name: Set kubeconfig with kubectl + run: | + kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" + kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" + kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" + kubectl config use-context "remote-context" + + # kubectl command steps ... # + ``` + +3. Create the full workflow + + - So we need to: + 1. Make sure the `kubectl` binary is available + 2. Checkout the repo + 3. Configure authentication with kubectl + 4. Run `kubectl` commands against the remote API + + ```yaml + # File: .github/workflows/kubectl.yaml + + name: Run kubectl against remote cluster + on: + push: + branches: + - "1-github-runner-manages-remote" + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Install kubectl + run: | + mkdir $HOME/bin + curl -Lf 'https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl' -o $HOME/bin/kubectl + chmod +x $HOME/bin/kubectl + echo "$HOME/bin" >> $GITHUB_PATH + + - name: Check kubectl is available on PATH + run: kubectl version --client + + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set kubeconfig with kubectl + run: | + kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" + kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" + kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" + kubectl config use-context "remote-context" + + - name: Run kubectl command against remote API + run: kubectl get namespaces + ``` From 5fb1315dae6b7613830953886af73e67437620fd Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:45:52 +0100 Subject: [PATCH 05/11] Fix curl command --- .github/workflows/kubectl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index 3798c2c..5d12462 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -11,7 +11,7 @@ jobs: - name: Install kubectl run: | mkdir $HOME/bin - curl -Lf 'https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl' -o $HOME/bin/kubectl + curl -Lf "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -o $HOME/bin/kubectl chmod +x $HOME/bin/kubectl echo "$HOME/bin" >> $GITHUB_PATH From a99387666dbbcab93b79d4ed6fdfbfec67cb5d07 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:11:45 +0100 Subject: [PATCH 06/11] Kubectl apply test on remote --- .github/workflows/kubectl.yml | 5 ++++- manifests/nginx-test.yml | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 manifests/nginx-test.yml diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index 5d12462..a9fdd9c 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -28,5 +28,8 @@ jobs: kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" kubectl config use-context "remote-context" - - name: Run kubectl command against remote API + - name: Check kubectl has authenticated/authorized access to the remote API run: kubectl get namespaces + + - name: kubectl apply with a file + run: kubectl apply -f "${HOME}/${GITHUB_REPOSITORY}/manifests/nginx-test.yml" diff --git a/manifests/nginx-test.yml b/manifests/nginx-test.yml new file mode 100644 index 0000000..b1b472a --- /dev/null +++ b/manifests/nginx-test.yml @@ -0,0 +1,14 @@ +# Create an nginx pod + +apiVersion: v1 +kind: Pod +metadata: + name: nginx-pod + labels: + app: nginx +spec: + containers: + - name: nginx-container + image: nginx:latest + ports: + - containerPort: 80 From 2ea394ff8fd3e10737dc10c2255a843e283410c7 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:19:43 +0100 Subject: [PATCH 07/11] Possibly fix repo/workspace path? --- .github/workflows/kubectl.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index a9fdd9c..ea1579a 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -32,4 +32,5 @@ jobs: run: kubectl get namespaces - name: kubectl apply with a file - run: kubectl apply -f "${HOME}/${GITHUB_REPOSITORY}/manifests/nginx-test.yml" + run: | + kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" From 7b009e82b80e4e74ab11242c541deb1d70c72b28 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:24:05 +0100 Subject: [PATCH 08/11] Add section on `kubectl apply -f` --- .gitignore | 1 - kubectl-gh-actions.md | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8c10ca5..7b5172f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ kube-config *.crt *.key -kubectl diff --git a/kubectl-gh-actions.md b/kubectl-gh-actions.md index aae6127..cfafa9b 100644 --- a/kubectl-gh-actions.md +++ b/kubectl-gh-actions.md @@ -29,7 +29,7 @@ kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" kubectl config use-context "remote-context" - # kubectl command steps ... # + # kubectl command steps ... # ``` 3. Create the full workflow @@ -47,7 +47,7 @@ on: push: branches: - - "1-github-runner-manages-remote" + - "main" jobs: deploy: runs-on: ubuntu-latest @@ -75,3 +75,14 @@ - name: Run kubectl command against remote API run: kubectl get namespaces ``` + + - With this, we have remote access to the API in according with the RBAC rules we created earlier + - If you wanted to `kubectl apply -f` in this action, you could do so like below: + + ```yaml + # previous setup steps # + + - name: kubectl apply with a file + run: | + kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" + ``` From ed99904cc9d394bcdb32a16bfc5ed2e50cb2aec1 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:35:16 +0100 Subject: [PATCH 09/11] Factor out cluster info into repo variables --- .github/workflows/kubectl.yml | 16 +++++-------- kubectl-gh-actions.md | 44 +++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index ea1579a..256cfd3 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -1,6 +1,6 @@ name: Run kubectl against remote cluster on: - workflow_dispatch: + workflow_dispatch: # Allows manual start of workflows push: branches: - "1-github-runner-manages-remote" @@ -23,14 +23,10 @@ jobs: - name: Set kubeconfig with kubectl run: | - kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" - kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" - kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" - kubectl config use-context "remote-context" + kubectl config set-cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --server "${{ secrets.KUBE_API_SERVER_ADDR }}" + kubectl config set-credentials "${{ vars.KUBE_REMOTE_USER }}" --token "${{ secrets.KUBE_JWT_AUTH_TOKEN }}" + kubectl config set-context "${{ vars.KUBE_REMOTE_CONTEXT }}" --cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --user "${{ vars.KUBE_REMOTE_USER }}" + kubectl config use-context "${{ vars.KUBE_REMOTE_CONTEXT }}" - - name: Check kubectl has authenticated/authorized access to the remote API + - name: Run kubectl command against remote API run: kubectl get namespaces - - - name: kubectl apply with a file - run: | - kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" diff --git a/kubectl-gh-actions.md b/kubectl-gh-actions.md index cfafa9b..22c5662 100644 --- a/kubectl-gh-actions.md +++ b/kubectl-gh-actions.md @@ -2,7 +2,7 @@ # Steps -1. Add secrets for the server address and JWT token +1. Add secrets for the server address and JWT token, and other variables - Note that generated JWT tokens are relatively short-lived, but you can extend their validity by passing `--duration=` to `kubectl create token` - e.g. `kubectl create-token remote-dev --duration=12h` for a token valid for 12 hours @@ -10,13 +10,21 @@ - On the webpage for your repo: - Settings -> Secrets and Variables -> Actions -> New Repository Secret - - Set the name to `JWT_AUTH_TOKEN` + - Set the name to `KUBE_JWT_AUTH_TOKEN` - Set the value to the JWT token you generated - - Add another secret called `API_SERVER_ADDR` with the value of your public-facing API server address + - Add another secret called `KUBE_API_SERVER_ADDR` with the value of your public-facing API server address -2. Access the secret in the action + - We'll also add some variables for the cluster, remote username, and remote context + - On the webpage for your repo: + - Settings -> Secrets and Variables -> Actions -> Variables -> New Repository Variable + - Add three variables with the names and values: + - KUBE_REMOTE_CLUSTER = minikube + - KUBE_REMOTE_USER = remote-dev + - KUBE_REMOTE_CONTEXT = remote-context + +2. Access the secrets and variables in the action - - Github actions can access repository secrets using the syntax `${{ secrets. }}` + - Github actions can access repository secrets using the syntax `${{ secrets. }}` and variables with `${{ vars. }}` - We'll create a step in our action that sets the correct kubeconfig ```yaml @@ -24,14 +32,16 @@ - name: Set kubeconfig with kubectl run: | - kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" - kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" - kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" - kubectl config use-context "remote-context" + kubectl config set-cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --server "${{ secrets.KUBE_API_SERVER_ADDR }}" + kubectl config set-credentials "${{ vars.KUBE_REMOTE_USER }}" --token "${{ secrets.KUBE_JWT_AUTH_TOKEN }}" + kubectl config set-context "${{ vars.KUBE_REMOTE_CONTEXT }}" --cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --user "${{ vars.KUBE_REMOTE_USER }}" + kubectl config use-context "${{ vars.KUBE_REMOTE_CONTEXT }}" # kubectl command steps ... # ``` + - Using these variables and secrets makes it easier to update them in the future, without modifying the workflow file directly + 3. Create the full workflow - So we need to: @@ -45,6 +55,7 @@ name: Run kubectl against remote cluster on: + workflow_dispatch: # Allows manual start of workflows push: branches: - "main" @@ -54,8 +65,8 @@ steps: - name: Install kubectl run: | - mkdir $HOME/bin - curl -Lf 'https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl' -o $HOME/bin/kubectl + mkdir "$HOME/bin" + curl -Lf "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -o "$HOME/bin/kubectl" chmod +x $HOME/bin/kubectl echo "$HOME/bin" >> $GITHUB_PATH @@ -67,10 +78,10 @@ - name: Set kubeconfig with kubectl run: | - kubectl config set-cluster "minikube" --server "${{ secrets.API_SERVER_ADDR }}" - kubectl config set-credentials "remote-dev" --token "${{ secrets.JWT_AUTH_TOKEN }}" - kubectl config set-context "remote-context" --cluster "minikube" --user "remote-dev" - kubectl config use-context "remote-context" + kubectl config set-cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --server "${{ secrets.KUBE_API_SERVER_ADDR }}" + kubectl config set-credentials "${{ vars.KUBE_REMOTE_USER }}" --token "${{ secrets.KUBE_JWT_AUTH_TOKEN }}" + kubectl config set-context "${{ vars.KUBE_REMOTE_CONTEXT }}" --cluster "${{ vars.KUBE_REMOTE_CLUSTER }}" --user "${{ vars.KUBE_REMOTE_USER }}" + kubectl config use-context "${{ vars.KUBE_REMOTE_CONTEXT }}" - name: Run kubectl command against remote API run: kubectl get namespaces @@ -83,6 +94,5 @@ # previous setup steps # - name: kubectl apply with a file - run: | - kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" + run: kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" ``` From 92d65f5dd510d5a0bb5c7b2336f92c4fa1f804c5 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:39:01 +0100 Subject: [PATCH 10/11] Re-add apply to kubectl.yml --- .github/workflows/kubectl.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index 256cfd3..7e4d413 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -30,3 +30,6 @@ jobs: - name: Run kubectl command against remote API run: kubectl get namespaces + + - name: kubectl apply with a file + run: kubectl apply -f "${GITHUB_WORKSPACE}/manifests/nginx-test.yml" From c5b97bb2589cee7bf4f063ac80a2aba66f73da89 Mon Sep 17 00:00:00 2001 From: Red <78027161+NoSpawnn@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:45:40 +0100 Subject: [PATCH 11/11] Ignore paths in workflow --- .github/workflows/kubectl.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/kubectl.yml b/.github/workflows/kubectl.yml index 7e4d413..ba99f1e 100644 --- a/.github/workflows/kubectl.yml +++ b/.github/workflows/kubectl.yml @@ -1,9 +1,14 @@ name: Run kubectl against remote cluster + on: workflow_dispatch: # Allows manual start of workflows push: branches: - "1-github-runner-manages-remote" + paths-ignore: + - *.md + - *.example + jobs: deploy: runs-on: ubuntu-latest