feat(actions): bootstrap sol runner
All checks were successful
runner-smoke / smoke (push) Successful in 36s
All checks were successful
runner-smoke / smoke (push) Successful in 36s
This commit is contained in:
45
.gitea/workflows/runner-smoke.yaml
Normal file
45
.gitea/workflows/runner-smoke.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: runner-smoke
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- .gitea/workflows/runner-smoke.yaml
|
||||||
|
- bootstrap/gitea-actions/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
smoke:
|
||||||
|
runs-on: k3s-deploy
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Materialize kubeconfig
|
||||||
|
env:
|
||||||
|
K3S_KUBECONFIG_B64: ${{ secrets.K3S_KUBECONFIG_B64 }}
|
||||||
|
run: |
|
||||||
|
test -n "$K3S_KUBECONFIG_B64"
|
||||||
|
printf '%s' "$K3S_KUBECONFIG_B64" | base64 -d >/tmp/kubeconfig
|
||||||
|
chmod 600 /tmp/kubeconfig
|
||||||
|
|
||||||
|
- name: Verify repository checkout
|
||||||
|
run: |
|
||||||
|
pwd
|
||||||
|
ls -la
|
||||||
|
test -f bootstrap/gitea-actions/kustomization.yaml
|
||||||
|
|
||||||
|
- name: Install kubectl
|
||||||
|
run: |
|
||||||
|
curl -fsSL -o /tmp/kubectl https://dl.k8s.io/release/v1.34.6/bin/linux/amd64/kubectl
|
||||||
|
install -m 0755 /tmp/kubectl /usr/local/bin/kubectl
|
||||||
|
kubectl version --client
|
||||||
|
|
||||||
|
- name: Verify cluster access
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kubeconfig
|
||||||
|
run: |
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
kubectl -n gitea-actions get deploy,pods
|
||||||
|
kubectl -n trade-infra get svc,endpointslices
|
||||||
13
README.md
Normal file
13
README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# trade-gitops
|
||||||
|
|
||||||
|
GitOps bootstrap and cluster runtime for the `trade-next` migration target on `mevnode_sol`.
|
||||||
|
|
||||||
|
## Bootstrap Modules
|
||||||
|
|
||||||
|
- `bootstrap/gitea-actions`: organization-scoped Gitea Actions runner for `trade-next`, including deployer RBAC and operator scripts.
|
||||||
|
|
||||||
|
## Current Scope
|
||||||
|
|
||||||
|
- Bootstrap a single-node `k3s` control plane on `sol`.
|
||||||
|
- Expose host-level `Postgres` and `Redis` into the cluster through `trade-infra`.
|
||||||
|
- Run an organization-scoped Gitea Actions runner that can execute deployment workflows against `sol`.
|
||||||
33
bootstrap/gitea-actions/README.md
Normal file
33
bootstrap/gitea-actions/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Gitea Actions Runner Bootstrap
|
||||||
|
|
||||||
|
This module bootstraps a single organization-scoped Gitea Actions runner for `trade-next` on the `sol` cluster.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
- Runner scope: organization-level for `trade-next`
|
||||||
|
- Runtime: `docker.io/gitea/act_runner:latest`
|
||||||
|
- Job execution: `docker:27-dind` sidecar with a shared Unix socket
|
||||||
|
- Cluster access for workflows: dedicated `trade-gitops-deployer` service account, exported as the `K3S_KUBECONFIG_B64` org secret
|
||||||
|
- Storage model: small persistent `hostPath` only for runner registration state, ephemeral Docker layer cache
|
||||||
|
- Runner labels: `ubuntu-latest` and `k3s-deploy`, both starting from the standard Gitea runner image so deployment jobs can install the exact `kubectl` version they need
|
||||||
|
|
||||||
|
## Operator Flow
|
||||||
|
|
||||||
|
1. Prepare the org registration token secret in `gitea-actions`.
|
||||||
|
2. Apply the kustomize module on `sol`.
|
||||||
|
3. Create or refresh the deployer kubeconfig and sync it to the `trade-next` org secrets.
|
||||||
|
4. Push a workflow to `trade-gitops` and let the runner execute deployment jobs.
|
||||||
|
|
||||||
|
## Bootstrap Commands
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bootstrap/gitea-actions/scripts/bootstrap-sol.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This runner is intentionally pinned to the `sol` node because the target cluster is currently single-node.
|
||||||
|
- The deployer binding is `cluster-admin` for the first bootstrap pass and should be narrowed once the GitOps surface is fully reconstructed.
|
||||||
|
- The runner exposes the labels `ubuntu-latest` and `k3s-deploy`.
|
||||||
10
bootstrap/gitea-actions/kustomization.yaml
Normal file
10
bootstrap/gitea-actions/kustomization.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
namespace: gitea-actions
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- rbac.yaml
|
||||||
|
- runner-configmap.yaml
|
||||||
|
- runner-deployment.yaml
|
||||||
7
bootstrap/gitea-actions/namespace.yaml
Normal file
7
bootstrap/gitea-actions/namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: gitea-actions
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: gitea-actions
|
||||||
|
app.kubernetes.io/part-of: trade-gitops
|
||||||
18
bootstrap/gitea-actions/rbac.yaml
Normal file
18
bootstrap/gitea-actions/rbac.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: trade-gitops-deployer
|
||||||
|
namespace: gitea-actions
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: trade-gitops-deployer-cluster-admin
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: trade-gitops-deployer
|
||||||
|
namespace: gitea-actions
|
||||||
18
bootstrap/gitea-actions/runner-configmap.yaml
Normal file
18
bootstrap/gitea-actions/runner-configmap.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: act-runner-config
|
||||||
|
namespace: gitea-actions
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
runner:
|
||||||
|
file: /data/.runner
|
||||||
|
capacity: 1
|
||||||
|
timeout: 3h
|
||||||
|
cache:
|
||||||
|
enabled: false
|
||||||
|
container:
|
||||||
|
docker_host: unix:///var/run/docker.sock
|
||||||
|
force_pull: true
|
||||||
90
bootstrap/gitea-actions/runner-deployment.yaml
Normal file
90
bootstrap/gitea-actions/runner-deployment.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: trade-next-act-runner
|
||||||
|
namespace: gitea-actions
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: trade-next-act-runner
|
||||||
|
app.kubernetes.io/part-of: trade-gitops
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: trade-next-act-runner
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: trade-next-act-runner
|
||||||
|
app.kubernetes.io/part-of: trade-gitops
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: sol
|
||||||
|
volumes:
|
||||||
|
- name: runner-data
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/trade-gitops/gitea-actions/runner-data
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
- name: runner-config
|
||||||
|
configMap:
|
||||||
|
name: act-runner-config
|
||||||
|
- name: docker-sock
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: 1Gi
|
||||||
|
- name: dind-data
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: 20Gi
|
||||||
|
containers:
|
||||||
|
- name: dind
|
||||||
|
image: docker:27-dind
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
env:
|
||||||
|
- name: DOCKER_TLS_CERTDIR
|
||||||
|
value: ""
|
||||||
|
args:
|
||||||
|
- --host=unix:///var/run/docker.sock
|
||||||
|
- --group=1001
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: docker-sock
|
||||||
|
mountPath: /var/run
|
||||||
|
- name: dind-data
|
||||||
|
mountPath: /var/lib/docker
|
||||||
|
- name: runner
|
||||||
|
image: docker.io/gitea/act_runner:latest
|
||||||
|
env:
|
||||||
|
- name: CONFIG_FILE
|
||||||
|
value: /config/config.yaml
|
||||||
|
- name: GITEA_INSTANCE_URL
|
||||||
|
value: https://gitea.mpabi.pl
|
||||||
|
- name: GITEA_RUNNER_NAME
|
||||||
|
value: trade-next-sol
|
||||||
|
- name: GITEA_RUNNER_LABELS
|
||||||
|
value: ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,k3s-deploy:docker://docker.gitea.com/runner-images:ubuntu-latest
|
||||||
|
- name: GITEA_RUNNER_REGISTRATION_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: act-runner-registration-token
|
||||||
|
key: token
|
||||||
|
- name: DOCKER_HOST
|
||||||
|
value: unix:///var/run/docker.sock
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: runner-data
|
||||||
|
mountPath: /data
|
||||||
|
- name: runner-config
|
||||||
|
mountPath: /config
|
||||||
|
- name: docker-sock
|
||||||
|
mountPath: /var/run
|
||||||
28
bootstrap/gitea-actions/scripts/bootstrap-sol.sh
Executable file
28
bootstrap/gitea-actions/scripts/bootstrap-sol.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
|
MODULE_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd -P)"
|
||||||
|
REPO_ROOT="$(cd -- "${MODULE_DIR}/../.." && pwd -P)"
|
||||||
|
|
||||||
|
SOL_HOST="${SOL_HOST:-149.50.96.162}"
|
||||||
|
SOL_USER="${SOL_USER:-user}"
|
||||||
|
SOL_SSH_KEY="${SOL_SSH_KEY:-/home/user/dev/mcp/keys/mpabi/mevnode_mcp}"
|
||||||
|
REMOTE_STAGING_DIR="${REMOTE_STAGING_DIR:-/tmp/trade-gitops-bootstrap}"
|
||||||
|
|
||||||
|
ssh_sol() {
|
||||||
|
ssh -i "$SOL_SSH_KEY" -o IdentitiesOnly=yes -o StrictHostKeyChecking=no "$SOL_USER@$SOL_HOST" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
"${SCRIPT_DIR}/create-runner-registration-secret.sh"
|
||||||
|
|
||||||
|
tar -C "$REPO_ROOT" -cf - bootstrap/gitea-actions | \
|
||||||
|
ssh_sol "rm -rf ${REMOTE_STAGING_DIR} && mkdir -p ${REMOTE_STAGING_DIR} && tar -C ${REMOTE_STAGING_DIR} -xf -"
|
||||||
|
|
||||||
|
ssh_sol "sudo mkdir -p /var/lib/trade-gitops/gitea-actions/runner-data"
|
||||||
|
ssh_sol "sudo k3s kubectl apply -k ${REMOTE_STAGING_DIR}/bootstrap/gitea-actions"
|
||||||
|
ssh_sol "sudo k3s kubectl -n gitea-actions rollout status deploy/trade-next-act-runner --timeout=180s"
|
||||||
|
|
||||||
|
"${SCRIPT_DIR}/sync-k3s-kubeconfig-org-secret.sh"
|
||||||
|
|
||||||
|
ssh_sol "sudo k3s kubectl -n gitea-actions get pods -o wide"
|
||||||
44
bootstrap/gitea-actions/scripts/create-runner-registration-secret.sh
Executable file
44
bootstrap/gitea-actions/scripts/create-runner-registration-secret.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ORG="${ORG:-trade-next}"
|
||||||
|
GITEA_URL="${GITEA_URL:-https://gitea.mpabi.pl}"
|
||||||
|
GITEA_TOKEN_FILE="${GITEA_TOKEN_FILE:-/home/user/dev/mcp/tools/tokens/gitea.token}"
|
||||||
|
SOL_HOST="${SOL_HOST:-149.50.96.162}"
|
||||||
|
SOL_USER="${SOL_USER:-user}"
|
||||||
|
SOL_SSH_KEY="${SOL_SSH_KEY:-/home/user/dev/mcp/keys/mpabi/mevnode_mcp}"
|
||||||
|
NAMESPACE="${NAMESPACE:-gitea-actions}"
|
||||||
|
SECRET_NAME="${SECRET_NAME:-act-runner-registration-token}"
|
||||||
|
|
||||||
|
gitea_token() {
|
||||||
|
cut -d: -f2- "$GITEA_TOKEN_FILE" | head -n1 | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_sol() {
|
||||||
|
ssh -i "$SOL_SSH_KEY" -o IdentitiesOnly=yes -o StrictHostKeyChecking=no "$SOL_USER@$SOL_HOST" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
API_TOKEN="$(gitea_token)"
|
||||||
|
if [ -z "$API_TOKEN" ]; then
|
||||||
|
echo "Gitea API token is empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REG_TOKEN="$(
|
||||||
|
curl -fsS \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${API_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/orgs/${ORG}/actions/runners/registration-token" \
|
||||||
|
| jq -r '.token'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ -z "$REG_TOKEN" ] || [ "$REG_TOKEN" = "null" ]; then
|
||||||
|
echo "Failed to obtain runner registration token" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssh_sol "sudo k3s kubectl get ns ${NAMESPACE} >/dev/null 2>&1 || sudo k3s kubectl create ns ${NAMESPACE} >/dev/null"
|
||||||
|
|
||||||
|
printf '%s' "$REG_TOKEN" | ssh_sol "tmp=\$(mktemp); cat >\"\$tmp\"; sudo k3s kubectl -n ${NAMESPACE} create secret generic ${SECRET_NAME} --from-file=token=\"\$tmp\" --dry-run=client -o yaml | sudo k3s kubectl apply -f - >/dev/null; rm -f \"\$tmp\""
|
||||||
|
|
||||||
|
echo "Runner registration secret synced to ${SOL_HOST}:${NAMESPACE}/${SECRET_NAME}"
|
||||||
70
bootstrap/gitea-actions/scripts/sync-k3s-kubeconfig-org-secret.sh
Executable file
70
bootstrap/gitea-actions/scripts/sync-k3s-kubeconfig-org-secret.sh
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ORG="${ORG:-trade-next}"
|
||||||
|
SECRET_NAME="${SECRET_NAME:-K3S_KUBECONFIG_B64}"
|
||||||
|
GITEA_URL="${GITEA_URL:-https://gitea.mpabi.pl}"
|
||||||
|
GITEA_TOKEN_FILE="${GITEA_TOKEN_FILE:-/home/user/dev/mcp/tools/tokens/gitea.token}"
|
||||||
|
SOL_HOST="${SOL_HOST:-149.50.96.162}"
|
||||||
|
SOL_USER="${SOL_USER:-user}"
|
||||||
|
SOL_SSH_KEY="${SOL_SSH_KEY:-/home/user/dev/mcp/keys/mpabi/mevnode_mcp}"
|
||||||
|
DEPLOY_NAMESPACE="${DEPLOY_NAMESPACE:-gitea-actions}"
|
||||||
|
DEPLOY_SERVICE_ACCOUNT="${DEPLOY_SERVICE_ACCOUNT:-trade-gitops-deployer}"
|
||||||
|
KUBE_API_SERVER="${KUBE_API_SERVER:-https://149.50.96.162:6443}"
|
||||||
|
|
||||||
|
gitea_token() {
|
||||||
|
cut -d: -f2- "$GITEA_TOKEN_FILE" | head -n1 | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_sol() {
|
||||||
|
ssh -i "$SOL_SSH_KEY" -o IdentitiesOnly=yes -o StrictHostKeyChecking=no "$SOL_USER@$SOL_HOST" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
API_TOKEN="$(gitea_token)"
|
||||||
|
if [ -z "$API_TOKEN" ]; then
|
||||||
|
echo "Gitea API token is empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CA_DATA="$(ssh_sol "sudo k3s kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}'")"
|
||||||
|
SA_TOKEN="$(ssh_sol "sudo k3s kubectl -n ${DEPLOY_NAMESPACE} create token ${DEPLOY_SERVICE_ACCOUNT} --duration=8760h")"
|
||||||
|
|
||||||
|
if [ -z "$CA_DATA" ] || [ -z "$SA_TOKEN" ]; then
|
||||||
|
echo "Failed to generate deployer kubeconfig material" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
KUBECONFIG_B64="$(
|
||||||
|
cat <<EOF | base64 -w0
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority-data: ${CA_DATA}
|
||||||
|
server: ${KUBE_API_SERVER}
|
||||||
|
name: sol
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: sol
|
||||||
|
namespace: default
|
||||||
|
user: ${DEPLOY_SERVICE_ACCOUNT}
|
||||||
|
name: sol
|
||||||
|
current-context: sol
|
||||||
|
users:
|
||||||
|
- name: ${DEPLOY_SERVICE_ACCOUNT}
|
||||||
|
user:
|
||||||
|
token: ${SA_TOKEN}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
|
||||||
|
PAYLOAD="$(jq -nc --arg data "$KUBECONFIG_B64" --arg description "k3s deploy kubeconfig for trade-next on sol" '{data:$data,description:$description}')"
|
||||||
|
|
||||||
|
curl -fsS \
|
||||||
|
-X PUT \
|
||||||
|
-H "Authorization: token ${API_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
"${GITEA_URL}/api/v1/orgs/${ORG}/actions/secrets/${SECRET_NAME}" \
|
||||||
|
>/dev/null
|
||||||
|
|
||||||
|
echo "Organization secret ${ORG}/${SECRET_NAME} updated"
|
||||||
Reference in New Issue
Block a user