⚡ Batteries-included Golang microservice template ⚡️
Last updated: 06/11/2024
It includes:
Makefile
that is used for run, test, build, deploy actionsDockerfile
for building a Docker image (alpine
with multi-stage build)docker-compose.yml
for local dev- Github workflows for PR and release automation
- Sane code layout [1]
- Structured logging
- Good health-checking practices (uses async health-checking)
- Sample kubernetes deploy configs
- Configurable profiling support (pprof)
- Pre-instrumented with New Relic APM
- DigitalOcean container registry support
It uses:
Go 1.22
julienschmidt/httprouter
for the HTTP routeruber/zap
for structured, light-weight loggingalecthomas/kong
for CLI args + ENV parsingnewrelic/go-agent
for APM (with logging)streamdal/rabbit
for reliable RabbitMQonsi/ginkgo
andonsi/gomega
for BDD-style testing
[1] main.go
for entrypoint, deps/deps.go
for dependency setup + simple
dependency injection in tests, backends
and services
abstraction for business
logic.
All actions are performed via make
- run make help
to see list of available make args (targets).
For example:
- To run the service, run
make run
- To build + push a docker img, run
make docker/build
- To deploy to staging, run
make k8s/deploy/stage
<- make sure to switch Kube context to staging!!! - To deploy to production, run
make k8s/deploy/prod
<- make sure to switch Kube context to production!!!
Secrets are stored in K8S using their native Secret
resource.
You can create them via kubectl
:
kubectl create secret generic my-secret --from-literal=secret-key=secret-value
You can then edit it: kubectl edit secret my-secret
NOTE: That the secret values are base64 encoded - when copy/pasting, make sure to decode them first:
❯ echo "dG9vdAo=" | base64 -D
toot
The secrets can be referenced as follows in the deploy config:
env:
- name: MY_SECRET
valueFrom:
secretKeyRef:
name: my-secret
key: secret-key
This service uses a custom logger that wraps uber/zap
in order to provide a
structured logging interface. While NR is able to collect logs written via uber/zap
,
it does not include any "initial fields" set on the logger.
This makes it very difficult to create temporary loggers with base values that
are re-used throughout a method. For example: In method A
that is 100 lines
long, we may want to create a logger with a base field "method" set to "A".
That would allow us to use the same logger throughout the method and not have to always include "method=A" attributes in each log message - the field will be included automatically.
The custom log wrapper provides this functionality.
PR and release automation is done via GitHub Actions.
When a PR is opened, a PR workflow is triggered.
When a PR is merged, a Release workflow is triggered. This workflow will build a docker image and push it to the DigitalOcean registry.
Deployment is manual. This is done for one primary reason:
A deployment is a critical operation that should be handled with care.
Or in other words, we do not throw deployments over the wall. Just because we can automate them, does not mean we should or will.
Deployments are performed via make k8s/deploy/stage
and make k8s/deploy/prod
.
Deployments are just kubectl apply -f deploy.stage.yaml
under the hood. The image
the deployment will use is the CURRENT short git sha in the repo!
- Click "Use this template" in Github to create a new repo
- Clone newly created repo
- Find & replace:
go-svc-template
-> lower case, dash separated service nameGO_SVC_TEMPLATE
-> upper case, underscore separated service name (for ENV vars)your_org
-> your Github org name
find . -maxdepth 3 -type f -exec sed -i "" 's/go-svc-template/service-name/g' {} \; find . -maxdepth 3 -type f -exec sed -i "" 's/GO_SVC_TEMPLATE/SERVICE_NAME/g' {} \; find . -maxdepth 3 -type f -exec sed -i "" 's/your_org/your-org-name/g' {} \; mv .github.rename .github
This template vendors packages by default to ensure reproducible builds + allow
local dev without an internet connection. Vendor can introduce its own headaches
though - if you want to remove it, remove -mod=vendor
in the Makefile
.