Skip to content

reload, restart or redeploy

context

Containerized application deployed on Kubernetes, manually or via GitOps, is composed of multiple objects from namespace, deployment, configmaps, secrets, services, and more. Most are static while other will be updated during the application lifecycle.

Most of these object defintions are static while others will be updated with no to significant impact on the application, e.g.:

  • changing a service impacts the access but not the application.
  • changing a data backend secret impacts the application with crashing, providing corrupted and/or outdated response.
  • changing a namespace or a deployment will require a redeployment of the application, meaning full disruption.

How can we address these use cases?

the application

The hello-path-go code mockup a web service with a third-party credential loop validation. If the flag value of my-secret is:

  • different than 4321 then it fails and retry after 10 secondes.
  • 4321 then it "validates" the credentials and start the webservice.

the code

The sources are available here.

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)

func main() {

    var (
        listenAddr   = flag.String("listen-addr", "0.0.0.0:8080", "Service IP and Port with default 0.0.0.0:8080")
        mySecret     = flag.String("mysecret", "1234", "Default secret is 1234")
        reloadEnable = flag.Bool("reload", false, "Enable the configuration reload capability.")
    )

    flag.Parse()

    // Intanciate a custom logger by function.
    hplog := log.New(os.Stdout, "[hello-path-go-main] ", log.LstdFlags)

    // Define a handler for pseudo GET requests.
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        path := r.URL.Path

        // conditional message based on the provided path.
        if path == "/mysecret" {
            // return value to the pseudo GET request
            fmt.Fprintf(w, "my little secret is %s", *mySecret)
            // log the pseudo GET request
            hplog.Println("GET ", path)
        } else if path != "/" {
            // return value to the pseudo GET request
            fmt.Fprintf(w, "hello from %s\n", path)
            // log the pseudo GET request
            hplog.Println("GET ", path)
        } else {
            // return value to the pseudo GET request
            fmt.Fprintf(w, "try to add /test as path\n")
            // log the pseudo GET request
            hplog.Println("GET ", path)
        }
    })
    hplog.Println("------------------------------------------------------------")
    hplog.Println("hello-path-go - a simple web service returning the URL path.")
    hplog.Println("------------------------------------------------------------")
    hplog.Println("Web service initialization...")

    // reloadEnable help to wait for the appropriate secret to be provided.
    if *reloadEnable != false {
        reloadCount := 1
        hplog.Println("RELOAD: Connection to remote service: nok. Checking every 10 seconds.")
        for *mySecret != "4321" {
            hplog.Println("RELOAD(", reloadCount, "): mysecret value is", *mySecret, "while expected value is 4321.")
            reloadCount += 1
            time.Sleep(10 * time.Second)
        }
    }

    // check that the secret is correct to start the service.
    // if no, we crash.
    if *mySecret != "4321" {
        hplog.Println("Note: mysecret value is", *mySecret, "while expected value is 4321.")
        hplog.Fatalln("FATAL: connection to remote service failed. Check mysecret parameter.")
    }

    // if yes, we start the service.
    hplog.Println("Connection to remote service: ok.")
    hplog.Println("Web service accessible at", *listenAddr)

    // Start the service and listen on the given port.
    if err := http.ListenAndServe(*listenAddr, nil); err != nil {
        // Log error messages
        hplog.Fatal(err)
    }
}

To follow this article, the repository can clone with the following command:

git clone https://github.com/beezy-dev/verbose-couscous

Output of the code with no parameters results in an incorrect mysecret value:

go run docs/sources/hello-path-go/main.go

[hello-path-go-main] 2024/04/18 11:16:44 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:16:44 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 11:16:44 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:16:44 Web service initialization...
[hello-path-go-main] 2024/04/18 11:16:44 Note: mysecret value is 1234 while expected value is 4321.
[hello-path-go-main] 2024/04/18 11:16:44 FATAL: connection to remote service failed. Check mysecret parameter.
exit status 1

Output of the code with the correct value set for my-secret results in a working web service with a security exposure:

go run docs/sources/hello-path-go/main.go --mysecret 4321
[hello-path-go-main] 2024/04/18 11:17:59 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:17:59 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 11:17:59 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 11:17:59 Web service initialization...
[hello-path-go-main] 2024/04/18 11:17:59 Connection to remote service: ok.
[hello-path-go-main] 2024/04/18 11:17:59 Web service accessible at 0.0.0.0:8080

the build

The initial image has been built using podman with the following Containerfile:

FROM docker.io/library/golang:1.22.0-bullseye AS build

WORKDIR /work

# Copy the source
COPY ./* /work/
WORKDIR /work/

RUN CGO_ENABLED=1 GOOS=linux GO111MODULE=on go build -a -installsuffix cgo main.go

FROM quay.io/centos/centos:stream9-minimal

LABEL org.opencontainers.image.source=https://github.com/beezy-dev/kleidi 
LABEL org.opencontainers.image.title="hello-path-go" 
LABEL org.opencontainers.image.vendor="beeyz.dev" 
LABEL org.opencontainers.image.licenses="Apache-2.0" 
LABEL org.opencontainers.image.documentation="https://beezy.dev/application/envreload/"

COPY --from=build ./work/main ./main

ENTRYPOINT [ "./main" ]

To build the image, the following command can be executed:

podman build -t hello-path-go:v0.2 .

NOTE: at this stage, if you plan to use your build, the Deployment files will require some light modifications.

The image is hosted here https://github.com/beezy-dev/verbose-couscous/pkgs/container/hello-path-go The image tag is ghcr.io/beezy-dev/hello-path-go:v0.1 for any deployment type.

the deployment

no arg

The Deployment manfiest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-path-go-deployment
  labels:
    app: hello-path-go
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-path-go
  template:
    metadata:
      labels:
        app: hello-path-go
    spec:
      containers:
      - name: hello-path-go
        image: ghcr.io/beezy-dev/hello-path-go:v0.2
        imagePullPolicy: Always
        ports:
        - containerPort: 8080

To deploy hello-path-go:

kubectl apply -f docs/sources/hello-path-go/Deployment.yaml

Checking the Deployment status:

kubectl get all 
NAME                                            READY   STATUS             RESTARTS      AGE
pod/hello-path-go-deployment-5c48979c88-wfndp   0/1     CrashLoopBackOff   1 (12s ago)   14s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   12m

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-path-go-deployment   0/1     1            0           14s

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-path-go-deployment-5c48979c88   1         1         0       14s

Checking the logs from the Pod:

kubectl logs pod/hello-path-go-deployment-5c48979c88-fhl4w
[hello-path-go-main] 2024/04/18 18:52:08 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 18:52:08 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 18:52:08 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 18:52:08 Web service initialization...
[hello-path-go-main] 2024/04/18 18:52:08 Note: mysecret value is 1234 while expected value is 4321.
[hello-path-go-main] 2024/04/18 18:52:08 FATAL: connection to remote service failed. Check mysecret parameter.

The default secret doesn't allow the mockup connection resulting in a crash. Let's delete the Deployment.

kubectl delete -f docs/sources/hello-path-go/Deployment.yaml
deployment.apps "hello-path-go-deployment" deleted

mysecret arg

To confirm that our application works, the code includes a flag called mysecret setting the secret as an argument.
However, this is a clear security exposure that should never be considered for a production-grade environment.

The Deployment manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-path-go-deployment
  labels:
    app: hello-path-go
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-path-go
  template:
    metadata:
      labels:
        app: hello-path-go
    spec:
      containers:
      - name: hello-path-go
        image: ghcr.io/beezy-dev/hello-path-go:v0.2
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
        args:
          - --mysecret=4321

To deploy hello-path-go:

kubectl apply -f docs/sources/hello-path-go/Deployment-mysecret.yaml

Checking the Deployment status:

kubectl get all 
NAME                                            READY   STATUS    RESTARTS   AGE
pod/hello-path-go-deployment-54fdf8687b-hq9lh   1/1     Running   0          13s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   40m

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-path-go-deployment   1/1     1            1           13s

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-path-go-deployment-54fdf8687b   1         1         1       13s

Checking the logs from the Pod:

kubectl logs pod/hello-path-go-deployment-54fdf8687b-hq9lh
[hello-path-go-main] 2024/04/18 19:19:15 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 19:19:15 hello-path-go - a simple web service returning the URL path.
[hello-path-go-main] 2024/04/18 19:19:15 ------------------------------------------------------------
[hello-path-go-main] 2024/04/18 19:19:15 Web service initialization...
[hello-path-go-main] 2024/04/18 19:19:15 Connection to remote service: ok.
[hello-path-go-main] 2024/04/18 19:19:15 Web service accessible at 0.0.0.0:8080

The explicit secret export allow the mockup connection to succeed and start the web service.
However, this is a clear security exposure that should never be considered for a production-grade environment.

Let's delete the Deployment.

kubectl delete -f docs/sources/hello-path-go/Deployment-mysecret.yam
deployment.apps "hello-path-go-deployment" deleted

reload, restart, redeploy

redeploy

restart

reload