The container image built herein acts as a webhook endpoint, that will notify Flux of git push and image push events.
Flux has a notify
method in its API, but this is unsuitable to
exposing to the internet, because
- it handles a payload defined by the Flux API, rather than handling the events posted by GitHub etc.
- to use it as a webhook endpoint, you would have to expose the Flux API listener to the internet; and that is terribly, terribly unsafe.
flux-recv
understands
GitHub
push events (and ping events)DockerHub
image push eventsGitLab
push eventsBitbucket
push events
In short:
- construct a configuration, including shared secrets
- run
fluxcd/flux-recv
as a sidecar in the flux deployment - expose the flux-recv listener to the internet
- install webhooks at the source provider (e.g., GitHub)
- check if it works
These are explained in more depth in the following sections.
Each webhook you want to receive must have its own shared secret, which is supplied as a file mounted into the container.
In addition, there is a config YAML that tells flux-recv
which
secrets correspond to which kinds of webhook, so it knows what to
expect.
Here is how to make a Kubernetes Secret with a shared secret and a configuration in it:
- create the secret -- GitHub
recommends 20
bytes of randomness (NB I am using
print
rather thanputs
because newlines will mess things up):
$ ruby -rsecurerandom -e 'print SecureRandom.hex(20)' > ./github.key
- create a configuration that refers to it:
$ cat >> fluxrecv.yaml <<EOF
fluxRecvVersion: 1
endpoints:
- keyPath: github.key
source: GitHub
EOF
The value of source
is one of the sources supported (listed above,
and in sources.go
).
- create a kustomization.yaml that will construct the Secret for you:
$ cat >> kustomization.yaml <<EOF
secretGenerator:
- name: fluxrecv-config
files:
- github.key
- fluxrecv.yaml
generatorOptions:
disableNameSuffixHash: true
EOF
- use kubectl to apply the kustomization:
kubectl apply -k .
You now have a Kubernetes secret named fluxrecv-config
.
The ideal is to run flux-recv
as a sidecar to fluxd
, so that the
flux API is only exposed on localhost. The additional bits you need in
the flux deployment are:
- a volume definition that refers to the fluxrecv-config constructed above
- a container spec to run the flux-recv container itself
The first bit goes under .spec.template.volumes
:
# volumes:
- name: fluxrecv-config
secret:
secretName: fluxrecv-config
defaultMode: 0400
The second bit goes under .spec.template.containers
:
# containers:
- name: recv
image: fluxcd/flux-recv:0.2.0
imagePullPolicy: IfNotPresent
args:
- --config=/etc/fluxrecv/fluxrecv.yaml
ports:
- containerPort: 8080
volumeMounts:
- name: fluxrecv-config
mountPath: /etc/fluxrecv
You do not need to alter the container spec for the flux
container,
though you may want to supply the argument --listen=localhost:3030
to limit API access to localhost, if you don't already.
If you do restrict API access to localhost, make sure you also
- supply
--listen-metrics=:3031
- annotate the pod with
prometheus.io/port: "3031"
, so Prometheus knows which port to scrape).- remove any probes that rely on reaching the API
To expose the flux-recv listener to the internet so that webhooks can reach it, you will need to:
- make a Kubernetes Service for
flux-recv
; and, - either
- tell an Ingress to route requests to the service; OR
- use ngrok to tunnel requests through to flux-recv
To be able to route requests to flux-recv
from anywhere else, it
needs a Kubernetes Service. Here's a suitable definition:
$ cat > flux-recv-service.yaml <<EOF
---
apiVersion: v1
kind: Service
metadata:
name: flux-recv
spec:
type: ClusterIP
ports:
- name: recv
port: 8080
targetPort: 8080
selector:
name: flux
EOF
$ kubectl apply -f ./flux-recv-service.yaml
The selector assumes that your flux deployment (which now includes the
flux-recv sidecar) has a label name: flux
.
If running in the cloud, you will need to route though an Ingress or an analogue, and that will vary depending how you're already using that. However, these things will be in common:
-
the URLs at flux-recv are of the form
/hook/<digest>
, where<digest>
is the SHA265 digest of a hook's shared secret, hex-encoded. flux-recv will print out these endpoints when it starts, or you can calculate them with e.g.,sha256 -b ./github.key
; -
the backend will be the
flux-recv
service created previously, with the port8080
.
If running locally (e.g., while developing flux-recv itself), it will
be more convenient to use ngrok
to tunnel requests through to your
cluster.
In general, ngrok will create a new hostname each time it runs, and you will want to run it in its own deployment so it doesn't get restarted too much.
The kustomization in ./example/ngrok
contains an
almost complete configuration. You need to obtain an auth token by
signing up for an ngrok.com account, and paste it into the field
indicated in example/ngrok.yml
, before running
$ kubectl apply -k ./example
Alternatively, you can not sign up to ngrok.com, and remove the volume mount and
-config
arg from the deployment. In this case, you will need to restart ngrok every few hours to get a fresh tunnel, and reinstall any webhooks with the new hostname for the tunnel.
The host part of your webhook URLs will be something like
https://abcd1234.ngrok.io
, and it will change each time ngrok starts
(which is why we went to some trouble to run it in its own
deployment).
The example ngrok configuration includes a Service with a NodePort, so
you can access ngrok's web UI. You may wish to remove the NodePort, or
the service entirely, and use kubectl port-forward
instead.
Each webhook source has its own user interface or API for installing webhooks. In general though, you will usually need
- a URL for the webhook
- the shared secret
You can get the webhook URL for an endpoint by combining the host --
which will depend on how you exposed flux-recv
to the internet in
the previous step -- and the path, which is
'/hook/' + sha256sum(secret)
You can see the path in the log output of flux-recv
, or calculate
the digest yourself with, e.g.,
$ sha256sum -b ./github.key
The webhook URL in total will look something like
https://abcd1234.ngrok.io/hook/7962c728be656d9580d0ce9bda78320c946d8321a4ba7f31ea15c7f2d471bd26
The path may differ if you are routing through an ingress or load balancer.
GitHub (and others) require the shared secret, which you can take directly from the file created in the first step (be careful not to introduce extra characters into the file, if you load it in an editor).
The easiest way to do this is to watch fluxd's logs, and push a new commit (or image) to the repo for which you installed the hook.
kubectl logs deploy/flux -f
You should see a log message indicating that fluxd has either
- refreshed the git repo
ts=2019-11-20T16:29:26.871873132Z caller=loop.go:127 component=sync-loop event=refreshed url=ssh://git@github.com/squaremo/flux-example
- ignored the notification because it's the wrong branch
ts=2019-11-19T15:03:06.477412849Z caller=daemon.go:540 component=daemon msg="notified about unrelated change" url=git@github.com:squaremo/flux-example.git branch=refs/tags/flux-write-check
- scheduled an image repo refresh
ts=2019-11-20T16:34:30.134889169Z caller=warming.go:95 component=warmer priority=squaremo/helloworld
Some sources (e.g., GitHub, GitLab) let you test or re-rerun hooks from their webhook configuration, which can be useful for debugging. If you are using ngrok, it's also possible to re-run a hook from its dashboard.
It is safe to re-run hooks, because fluxd
treats notifications as a
trigger to refresh state, rather than as authoritative themselves. For
example, when informed of an image push, fluxd does not add the image
mentioned to its database -- it polls the image registry in question
to determine whether there is a new image.