15/11/2020 - GO, KUBERNETES, NGINX
We have an application where Nginx and Go containers are used. Nginx container proxies requests to Go container. Nginx exposes port 8080
and we map it to port 80
so it is accessible via port 80
. Nginx then talks to Go container via port 9090
which is a static port. We will be using Kubernetes volumes for the Nginx config instead of creating a custom image.
├── Makefile
├── deploy
│ └── k8s
│ ├── configmap.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── docker
│ └── dev
│ ├── docker-compose.yaml
│ ├── go
│ │ └── Dockerfile
│ └── nginx
│ ├── Dockerfile
│ └── default.conf
└── main.go
apiVersion: apps/v1
kind: Deployment
metadata:
name: address-finder-deployment
labels:
app: address-finder
spec:
replicas: 1
selector:
matchLabels:
app: address-finder
template:
metadata:
labels:
app: address-finder
spec:
containers:
- name: go
image: you/address-finder-go:latest
- name: nginx
image: nginx:1.19.4-alpine
ports:
- containerPort: 8080
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.conf
readOnly: true
volumes:
- name: nginx-config
configMap:
name: address-finder-config
What you need to know about configmap is that, the changes in here won't restart the pods so you will have to handle restarts yourself which is not the scope of this post.
apiVersion: v1
kind: ConfigMap
metadata:
name: address-finder-config
data:
nginx.conf: |
server {
listen 8080 default_server;
location / {
proxy_pass http://127.0.0.1:9090/;
}
}
apiVersion: v1
kind: Service
metadata:
name: address-finder-service
spec:
type: NodePort
selector:
app: address-finder
ports:
- protocol: TCP
port: 80
targetPort: 8080
#
# STAGE 1: build
#
FROM golang:1.15-alpine3.12 as build
WORKDIR /source
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/main main.go
#
# STAGE 2: run
#
FROM alpine:3.12 as run
COPY --from=build /source/bin/main /main
ENTRYPOINT ["/main"]
FROM nginx:1.19.4-alpine
COPY docker/dev/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
server {
listen 8080 default_server;
location / {
proxy_pass http://address-finder-go:9090/;
}
}
version: "3.4"
services:
address-finder-go:
container_name: "address-finder-go"
build:
context: "../.."
dockerfile: "docker/dev/go/Dockerfile"
address-finder-nginx:
container_name: "address-finder-nginx"
build:
context: "../.."
dockerfile: "docker/dev/nginx/Dockerfile"
ports:
- "80:8080"
depends_on:
- "address-finder-go"
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(r.Header.Get("X-Request-Id") + "\n"))
})
log.Println("go server is running on :9090")
log.Fatal(http.ListenAndServe(":9090", nil))
}
The docker-run
and docker-test
commands are for your local environment. You can use them to test your application in local environment.
## Build and run.
.PHONY: docker-run
docker-run:
docker-compose -f docker/dev/docker-compose.yaml build --no-cache
docker-compose -f docker/dev/docker-compose.yaml up
## Remove.
.PHONY: docker-down
docker-down:
docker-compose -f docker/dev/docker-compose.yaml down
docker system prune --volumes --force
## Build, tag and push application image to registry then clean up.
.PHONY: docker-push
docker-push:
docker build -t you/address-finder-go:latest -f ./docker/dev/go/Dockerfile .
docker push you/address-finder-go:latest
docker rmi you/address-finder-go:latest
docker system prune --volumes --force
## Build and run.
.PHONY: docker-test
docker-test:
curl --request GET 'http://localhost:80' --header 'X-Request-Id: my-request-id'
## -----------------------------------------------------------------------------------
## Apply secret and deploy application to kubernetes cluster.
.PHONY: k8s-deploy
k8s-deploy:
kubectl apply -f deploy/k8s/configmap.yaml
kubectl apply -f deploy/k8s/deployment.yaml
kubectl apply -f deploy/k8s/service.yaml
## Send a dummy request to the exposed pod on kubernetes.
.PHONY: k8s-test
k8s-test:
curl --request GET $(shell minikube service address-finder-service --url) --header 'X-Request-Id: my-request-id'
Let's test our application in our local environment to see if it works.
$ make docker-run
Recreating address-finder-go ... done
Recreating address-finder-nginx ... done
address-finder-go | 2020/11/15 21:50:14 go server is running on :9090
address-finder-nginx | /docker-entrypoint.sh: Configuration complete; ready for start up
$ make docker-test
curl --request GET 'http://localhost:80' --header 'X-Request-Id: my-request-id'
my-request-id
Let's now build and push our Docker image to Docker Hub. This image will be pulled when we run our deployment so it is important.
$ make push
docker build -t you/address-finder-go:latest -f ./docker/dev/go/Dockerfile .
Sending build context to Docker daemon 7.762MB
.......
$ make k8s-deploy
kubectl apply -f deploy/k8s/configmap.yaml
configmap/address-finder-config created
kubectl apply -f deploy/k8s/deployment.yaml
deployment.apps/address-finder-deployment created
kubectl apply -f deploy/k8s/service.yaml
service/address-finder-service created
Let's check ConfigMap and Pod details.
$ kubectl describe configmap address-finder-config
Name: address-finder-config
Namespace: default
Labels:
Annotations: ...
Data
====
nginx.conf:
----
server {
listen 8080 default_server;
location / {
proxy_pass http://127.0.0.1:9090/;
}
}
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
address-finder-deployment-68dd5b79b8-m6sgd 2/2 Running 0 110s
$ kubectl logs address-finder-deployment-68dd5b79b8-m6sgd go
2020/11/15 22:15:17 go server is running on :9090
$ kubectl logs address-finder-deployment-68dd5b79b8-m6sgd nginx
/docker-entrypoint.sh: Configuration complete; ready for start up
Let's not test in Kubernetes and see the logs.
$ make k8s-test
curl --request GET http://192.168.99.100:31615 --header 'X-Request-Id: my-request-id'
my-request-id
172.17.0.1 - - [15/Nov/2020:22:20:57 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.54.0" "-"