372
mkdocs/docs/projects/k3s/webhook.md
Normal file
372
mkdocs/docs/projects/k3s/webhook.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# Webhooks
|
||||
|
||||
Continuous integration on easy mode.
|
||||
Webhooks allow for a ton of functionality,
|
||||
but we are going to use it to kick off a kubernetes job.
|
||||
Effectivitely automating reloading content on a static website.
|
||||
|
||||
## Background
|
||||
|
||||
This docs website is a static site that is hosted inside an nginx container.
|
||||
The storage for these redundant pods is a longhorn rwx pvc that gets stood up.
|
||||
To initialize the storage a kubernetes job is run. This job does the following:
|
||||
|
||||
- git clones the `rskntroot/rskio` repo containing the artifacts required to render the site
|
||||
- executes the `mkdocs` command to render the static site
|
||||
|
||||
So what if when we push to github, we setup a webhook that tells kubernetes to kick off that job?
|
||||
Well, we achieve some form of automation.
|
||||
So how do we do this?
|
||||
|
||||
## Setup
|
||||
|
||||
### RBAC
|
||||
|
||||
=== "ServiceAccount"
|
||||
|
||||
``` yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: webhook-job-trigger
|
||||
```
|
||||
|
||||
=== "Dev Roles"
|
||||
|
||||
``` yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: job-creator
|
||||
namespace: dev
|
||||
rules:
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["create"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: job-creator-binding
|
||||
namespace: dev
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: webhook-job-trigger
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: job-creator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
=== "Prod Roles"
|
||||
|
||||
``` yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: job-creator
|
||||
namespace: prod
|
||||
rules:
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["create"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: job-creator-binding
|
||||
namespace: prod
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: webhook-job-trigger
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: job-creator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
```
|
||||
|
||||
### ConfigMap
|
||||
|
||||
We will create a config map from a directory including the following files.
|
||||
|
||||
#### ConvertJob
|
||||
|
||||
We are going to be using curl to call the kubernetes API directly,
|
||||
so we need to convert our job from yaml to json.
|
||||
|
||||
Convert the job to JSON and save to `etc/mkdocs-dev.json`
|
||||
|
||||
=== "Job"
|
||||
|
||||
``` yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
generateName: mkdocs-builder-
|
||||
namespace: dev
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 600
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: mkdocs
|
||||
image: squidfunk/mkdocs-material
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
git clone --single-branch -b dev https://github.com/rskntroot/rskio.git --depth 1 /docs
|
||||
cd /docs/mkdocs
|
||||
mkdocs build --site-dir /output
|
||||
volumeMounts:
|
||||
- name: mkdocs-storage
|
||||
mountPath: /output
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- name: mkdocs-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: mkdocs-pvc
|
||||
```
|
||||
|
||||
=== "Convert"
|
||||
|
||||
``` bash
|
||||
mkdir etc
|
||||
cat job.yml | yq -e -j | jq > etc/mkdocs-dev.json
|
||||
```
|
||||
|
||||
The following docs we will assume that you also created `etc/mkdocs-main.json`.
|
||||
|
||||
#### Hooks
|
||||
|
||||
create `etc/hooks.yaml`
|
||||
|
||||
=== "etc/hooks.yaml"
|
||||
|
||||
``` yaml
|
||||
- id: rskio-mkdocs
|
||||
execute-command: /etc/webhook/reload.sh
|
||||
command-working-directory: /etc/webhook
|
||||
response-message: payload received
|
||||
response-headers:
|
||||
- name: Access-Control-Allow-Origin
|
||||
value: "*"
|
||||
pass-arguments-to-command:
|
||||
- source: payload
|
||||
name: ref
|
||||
- source: payload
|
||||
name: repository.full_name
|
||||
trigger-rule:
|
||||
and:
|
||||
- match:
|
||||
type: value
|
||||
value: push
|
||||
parameter:
|
||||
source: header
|
||||
name: X-GitHub-Event
|
||||
- match:
|
||||
type: value
|
||||
value: rskntroot/rskio
|
||||
parameter:
|
||||
source: payload
|
||||
name: repository.full_name
|
||||
```
|
||||
|
||||
=== "Secret"
|
||||
|
||||
after testing come back to implement secrets
|
||||
|
||||
``` yaml
|
||||
trigger-rule:
|
||||
and:
|
||||
- match:
|
||||
type: payload-hmac-sha1
|
||||
secret: mysecret
|
||||
parameter:
|
||||
source: header
|
||||
name: X-Hub-Signature
|
||||
```
|
||||
|
||||
apply the `configmap` and rollout restart the webhook deployment
|
||||
|
||||
#### Command
|
||||
|
||||
``` bash title="etc/reload.sh"
|
||||
#!/bin/sh
|
||||
|
||||
REF=$1
|
||||
REPO=$2
|
||||
|
||||
dispatch() {
|
||||
NS=$1
|
||||
JOB_JSON=$2
|
||||
SA_PATH="/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
curl https://kubernetes.default.svc/apis/batch/v1/namespaces/${NS}/jobs \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer $(cat ${SA_PATH}/token)" \
|
||||
-H "Content-Type: application/json" \
|
||||
--cacert "${SA_PATH}/ca.crt" \
|
||||
-d "@${JOB_JSON}"
|
||||
}
|
||||
|
||||
docs(){
|
||||
case ${REF} in
|
||||
refs/heads/dev)
|
||||
dispatch dev "/etc/webhook/mkdocs-dev.json"
|
||||
;;
|
||||
refs/heads/main)
|
||||
dispatch prod "/etc/webhook/mkdocs-main.json"
|
||||
;;
|
||||
*)
|
||||
echo "skipping push to unsupported ref ${REF}"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
case ${REPO} in
|
||||
rskntroot/rskio)
|
||||
docs
|
||||
;;
|
||||
*)
|
||||
echo "skipping push to unsupported repo ${REPO}"
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
#### Create
|
||||
|
||||
once all resources in `etc` are created run the following command:
|
||||
|
||||
``` bash
|
||||
kubectl create configmap webhook-etc --from-file=etc
|
||||
```
|
||||
|
||||
if you need to update anything run the following:
|
||||
|
||||
``` bash
|
||||
kubectl delete configmap webhook-etc
|
||||
kubectl create configmap webhook-etc --from-file=etc
|
||||
```
|
||||
|
||||
### Resources
|
||||
|
||||
The following resources will complete the work
|
||||
|
||||
=== "Deployment"
|
||||
|
||||
``` yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: webhook-docs
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: webhook-docs
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: webhook-docs
|
||||
spec:
|
||||
serviceAccountName: webhook-job-trigger
|
||||
containers:
|
||||
- name: webhook-docs
|
||||
image: ghcr.io/linuxserver-labs/webhook:latest
|
||||
command: ["/app/webhook"]
|
||||
args:
|
||||
- -hooks=/etc/webhook/hooks.yaml
|
||||
- -hotreload
|
||||
- -verbose
|
||||
volumeMounts:
|
||||
- name: webhook-etc
|
||||
mountPath: /etc/webhook
|
||||
volumes:
|
||||
- name: webhook-etc
|
||||
configMap:
|
||||
name: webhook-etc
|
||||
defaultMode: 493 # 0755
|
||||
```
|
||||
|
||||
=== "Service"
|
||||
|
||||
``` yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: webhook
|
||||
spec:
|
||||
selector:
|
||||
app: webhook-docs
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 9000
|
||||
targetPort: 9000
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
=== "Certificate"
|
||||
|
||||
``` yaml
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: io-rsk-dev-hooks-tls
|
||||
spec:
|
||||
secretName: io-rsk-dev-hooks-tls
|
||||
issuerRef:
|
||||
name: dev-step-issuer
|
||||
kind: ClusterIssuer
|
||||
commonName: hooks.dev.rsk.io
|
||||
dnsNames:
|
||||
- hooks.dev.rsk.io
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
encoding: PKCS1
|
||||
size: 2048
|
||||
usages:
|
||||
- server auth
|
||||
- client auth
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
secretTemplate:
|
||||
annotations:
|
||||
kubeseal-secret: "true"
|
||||
labels:
|
||||
domain: hooks-dev-rsk-io
|
||||
```
|
||||
|
||||
=== "Ingress"
|
||||
|
||||
``` yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: webhook
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`hooks.dev.rsk.io`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: webhook
|
||||
port: 9000
|
||||
middlewares:
|
||||
- name: ratelimit
|
||||
tls:
|
||||
secretName: io-rsk-hooks-tls
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
``` bash
|
||||
curl -X POST https://hooks.dev.rsk.io/hooks/rskio-mkdocs \
|
||||
-H 'X-Github-Event: push' \
|
||||
-H 'Content-type: application-json' \
|
||||
-d '{"ref": "refs/heads/dev","repository": {"full_name":"rskntroot/rskio"}}'
|
||||
```
|
||||
|
||||
!!! note "Github needs access to a public domain for this to work."
|
||||
Reference in New Issue
Block a user