diff --git a/app_go/.gitignore b/app_go/.gitignore index d19c362672..dc14f1cbda 100644 --- a/app_go/.gitignore +++ b/app_go/.gitignore @@ -25,3 +25,6 @@ go.work # End of https://www.toptal.com/developers/gitignore/api/go + + +/persistent diff --git a/app_go/Dockerfile b/app_go/Dockerfile index b202a0b57f..0cc25f1a6a 100644 --- a/app_go/Dockerfile +++ b/app_go/Dockerfile @@ -1,5 +1,7 @@ FROM golang:1.22.0-alpine3.19 as builder +RUN ["mkdir", "/empty-dir"] + WORKDIR /usr/src/app/ COPY go.mod *.go /usr/src/app/ @@ -16,6 +18,8 @@ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates. EXPOSE 5000 +VOLUME /persistent COPY --from=builder /usr/src/app/catfact_webapp / USER 2004:2004 +COPY --from=builder --chown=2004:2004 /empty-dir /persistent ENTRYPOINT ["/catfact_webapp"] diff --git a/app_go/main.go b/app_go/main.go index c775c49b5f..063463b90e 100644 --- a/app_go/main.go +++ b/app_go/main.go @@ -1,9 +1,14 @@ package main import ( + "sync/atomic" + "encoding/binary" + "errors" + "path/filepath" "fmt" "log" "net/http" + "os" "time" "github.com/prometheus/client_golang/prometheus" @@ -11,7 +16,10 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) -func index(w http.ResponseWriter, r *http.Request) { +const visitsFile = "persistent/visits.bin" +var visits atomic.Uint64 + +func indexHandler(w http.ResponseWriter, r *http.Request) { fact, err := catFact() if err == nil { w.WriteHeader(http.StatusOK) @@ -22,6 +30,10 @@ func index(w http.ResponseWriter, r *http.Request) { } } +func visitsHandler(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, "%d", visits.Load()) +} + var ( reqCnt = promauto.NewCounter(prometheus.CounterOpts{ @@ -48,14 +60,42 @@ func noteTimeMiddleware(next http.Handler) http.Handler { }) } +func countVisitsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + visits.Add(1) // Atomic increment, no race condition here, so counter + // is always correct + buf := make([]byte, binary.MaxVarintLen64) + binary.LittleEndian.PutUint64(buf, visits.Load()) + // Race condition in the order in which threads will write the file out, + // so the file may not be correct, but as the same number of bytes is + // always written out, the counter in the file remains a valid number. + os.WriteFile(visitsFile, buf, 0644) + next.ServeHTTP(w, r) + }) +} + + +func init() { + _ = os.MkdirAll(filepath.Dir(visitsFile), 0755) + buf, err := os.ReadFile(visitsFile) + if err == nil { + visits.Store(binary.LittleEndian.Uint64(buf)) + } else if errors.Is(err, os.ErrNotExist) { + visits.Store(0) + } else { + panic(err) + } +} + func main() { businessLogic := http.NewServeMux() - businessLogic.Handle("/", asHandler(index)) + businessLogic.Handle("/", asHandler(indexHandler)) + businessLogic.Handle("/visits", asHandler(visitsHandler)) // Note: keeping /metrics under middleware too for consistency with app_py businessLogic.Handle("/metrics", promhttp.Handler()) - wrapped := noteTimeMiddleware(businessLogic) + wrapped := noteTimeMiddleware(countVisitsMiddleware(businessLogic)) hostPort := "0.0.0.0:5000" _, _ = fmt.Println("Listening on http://" + hostPort) diff --git a/app_go/main_test.go b/app_go/main_test.go index fef1008521..05ea99b9a2 100644 --- a/app_go/main_test.go +++ b/app_go/main_test.go @@ -13,7 +13,7 @@ import ( func TestFactLoads(t *testing.T) { w := httptest.NewRecorder() - index(w, nil) + indexHandler(w, nil) resp := w.Result() if resp.StatusCode != http.StatusOK { diff --git a/k8s/12.md b/k8s/12.md index 9cc9cb9a8c..5a2e5d6e77 100644 --- a/k8s/12.md +++ b/k8s/12.md @@ -27,3 +27,55 @@ $ kubectl exec app-py-7d7d86657c-twd5l -- cat /persistent/config.json {"mole": ["hamsters"], "hamster": ["moles"]} $ ``` + +## ConfigMap env + +```sh +$ helm install app-go . +NAME: app-go +LAST DEPLOYED: Mon Apr 22 19:26:23 2024 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +NOTES: +1. Get the application URL by running these commands: + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace default svc -w app-go-app-py' + export SERVICE_IP=$(kubectl get svc --namespace default app-go-app-py --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}") + echo http://$SERVICE_IP:5000 +$ kubectl get po +NAME READY STATUS RESTARTS AGE +app-go-app-py-59f4c5c69d-8fljp 1/1 Running 0 22s +app-go-app-py-59f4c5c69d-bv496 1/1 Running 0 22s +app-go-app-py-59f4c5c69d-jlz6q 1/1 Running 0 22s +app-go-app-py-59f4c5c69d-t2nnn 1/1 Running 0 22s +$ kubectl debug --image=gcc -it app-go-app-py-59f4c5c69d-8fljp --target=app-py +Targeting container "app-py". If you don't see processes from this container it may be because the container runtime doesn't support this feature. +Defaulting debug container name to debugger-864lv. +If you don't see a command prompt, try pressing enter. +root@app-go-app-py-59f4c5c69d-8fljp:/# echo 'main(){setreuid(geteuid(),geteuid());execl("/bin/sh","sh",0);}' > a.c +root@app-go-app-py-59f4c5c69d-8fljp:/# gcc -o a a.c +a.c:1:1: warning: return type defaults to 'int' [-Wimplicit-int] + 1 | main(){setreuid(geteuid(),geteuid());execl("/bin/sh","sh",0);} + | ^~~~ +a.c: In function 'main': +a.c:1:8: warning: implicit declaration of function 'setreuid' [-Wimplicit-function-declaration] + 1 | main(){setreuid(geteuid(),geteuid());execl("/bin/sh","sh",0);} + | ^~~~~~~~ +a.c:1:17: warning: implicit declaration of function 'geteuid' [-Wimplicit-function-declaration] + 1 | main(){setreuid(geteuid(),geteuid());execl("/bin/sh","sh",0);} + | ^~~~~~~ +a.c:1:38: warning: implicit declaration of function 'execl' [-Wimplicit-function-declaration] + 1 | main(){setreuid(geteuid(),geteuid());execl("/bin/sh","sh",0);} + | ^~~~~ +a.c:1:38: warning: incompatible implicit declaration of built-in function 'execl' [-Wbuiltin-declaration-mismatch] +root@app-go-app-py-59f4c5c69d-8fljp:/# chown 2004:2004 a +root@app-go-app-py-59f4c5c69d-8fljp:/# chmod u+s a +root@app-go-app-py-59f4c5c69d-8fljp:/# exec ./a +$ cat /proc/1/environ | sed 's/\x0/\n/g' | grep seal +seal=2001 +monk_seal=century +$ exit +Session ended, the ephemeral container will not be restarted but may be reattached using 'kubectl attach app-go-app-py-59f4c5c69d-8fljp -c debugger-864lv -i -t' if it is still running +$ +``` diff --git a/k8s/app-go/charts/label-lib-0.1.0.tgz b/k8s/app-go/charts/label-lib-0.1.0.tgz new file mode 100644 index 0000000000..73799af3ad Binary files /dev/null and b/k8s/app-go/charts/label-lib-0.1.0.tgz differ diff --git a/k8s/app-go/files/config.yaml b/k8s/app-go/files/config.yaml new file mode 100644 index 0000000000..9bbd41223a --- /dev/null +++ b/k8s/app-go/files/config.yaml @@ -0,0 +1,2 @@ +seal: "2001" +monk_seal: "century" diff --git a/k8s/app-go/templates/config-map.yaml b/k8s/app-go/templates/config-map.yaml new file mode 100644 index 0000000000..c6d388b3d7 --- /dev/null +++ b/k8s/app-go/templates/config-map.yaml @@ -0,0 +1,6 @@ +apiVersion: "v1" +kind: ConfigMap +metadata: + name: {{.Release.Name}}-config-map +data: +{{ .Files.Get "files/config.yaml" | indent 2 }} diff --git a/k8s/app-go/templates/deployment.yaml b/k8s/app-go/templates/deployment.yaml index 57e368e236..38e21c6ec2 100644 --- a/k8s/app-go/templates/deployment.yaml +++ b/k8s/app-go/templates/deployment.yaml @@ -52,6 +52,9 @@ spec: {{- end }} env: {{ include "app-go.environ" . | nindent 12 }} + envFrom: + - configMapRef: + name: {{.Release.Name}}-config-map {{- with .Values.volumes }} volumes: {{- toYaml . | nindent 8 }} diff --git a/k8s/app-go/values.yaml b/k8s/app-go/values.yaml index 14a6b3f2bb..38ba5d69de 100644 --- a/k8s/app-go/values.yaml +++ b/k8s/app-go/values.yaml @@ -4,7 +4,7 @@ image: repository: kolay0ne/app_go pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "lab8" + tag: "lab12" serviceAccount: # Specifies whether a service account should be created diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 95d0db6dd4..c24cb11d46 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -5,6 +5,7 @@ networks: volumes: grafana-storage: py-persistent: + go-persistent: services: app_py: @@ -22,7 +23,7 @@ services: - py-persistent:/app/persistent app_go: - image: kolay0ne/app_go:lab8 + image: kolay0ne/app_go:lab12 ports: - "5500:5000" logging: @@ -32,6 +33,8 @@ services: resources: {limits: {memory: 20M}} networks: - prometheus + volumes: + - go-persistent:/persistent loki: image: grafana/loki:2.9.2