diff --git a/clients/proxy-client/Dockerfile b/clients/proxy-client/Dockerfile new file mode 100644 index 0000000..8cf3a17 --- /dev/null +++ b/clients/proxy-client/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:alpine AS builder + +WORKDIR /go/src/app + +COPY . . + +RUN go build -o app + +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /go/src/app/app . + +ENV PROXY_URL="The proxy URL goes here." + +EXPOSE 8080 + +CMD ["./app"] diff --git a/clients/proxy-client/deploy/Chart.yaml b/clients/proxy-client/deploy/Chart.yaml new file mode 100644 index 0000000..bf35679 --- /dev/null +++ b/clients/proxy-client/deploy/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: snappcloud-proxy-client +description: A Helm chart for Kubernetes + +type: application +version: 0.1.0 +appVersion: "0.0.1" diff --git a/clients/proxy-client/deploy/templates/deployment.yaml b/clients/proxy-client/deploy/templates/deployment.yaml new file mode 100644 index 0000000..2b4c774 --- /dev/null +++ b/clients/proxy-client/deploy/templates/deployment.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "snappcloud-proxy-client.fullname" . }} + labels: + {{- include "snappcloud-proxy-client.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "snappcloud-proxy-client.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "snappcloud-proxy-client.selectorLabels" . | nindent 8 }} + annotations: + {{- .Values.podAnnotations | toYaml | nindent 8 }} + spec: + serviceAccountName: {{ include "snappcloud-proxy-client.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + env: + - name: PROXY_URL + valueFrom: + secretKeyRef: + name: {{ .Values.configSecretName }} + key: PROXY_URL + {{ .Values.nodeSelector | toYaml | nindent 8 }} + tolerations: + {{- with .Values.tolerations }} + {{- toYaml . | nindent 8 }} + {{- end }} + affinity: + {{- with .Values.affinity }} + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/clients/proxy-client/deploy/templates/secret.yaml b/clients/proxy-client/deploy/templates/secret.yaml new file mode 100644 index 0000000..67599b5 --- /dev/null +++ b/clients/proxy-client/deploy/templates/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.configSecretName }} + labels: + {{- include "snappcloud-proxy-client.labels" . | nindent 4 }} +type: Opaque +stringData: + PROXY_URL: "The proxy address." diff --git a/clients/proxy-client/deploy/templates/service.yaml b/clients/proxy-client/deploy/templates/service.yaml new file mode 100644 index 0000000..337d0a1 --- /dev/null +++ b/clients/proxy-client/deploy/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "snappcloud-proxy-client.fullname" . }} + labels: + {{- include "snappcloud-proxy-client.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + selector: + {{- include "snappcloud-proxy-client.selectorLabels" . | nindent 4 }} diff --git a/clients/proxy-client/deploy/values.yaml b/clients/proxy-client/deploy/values.yaml new file mode 100644 index 0000000..ef6843e --- /dev/null +++ b/clients/proxy-client/deploy/values.yaml @@ -0,0 +1,37 @@ +replicaCount: 1 + +image: + repository: ghcr.io/snapp-incubator/snappcloud-status-backend + pullPolicy: Always + tag: "0.0.1" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: false + automount: true + annotations: {} + name: "" + +service: + type: ClusterIP + port: 8080 + +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +configSecretName: spcld-proxy-health-client diff --git a/clients/proxy-client/go.mod b/clients/proxy-client/go.mod new file mode 100644 index 0000000..79ba04b --- /dev/null +++ b/clients/proxy-client/go.mod @@ -0,0 +1,16 @@ +module github.com/snapp-incubator/snappcloud-status-backend/clients/proxy-client + +go 1.21.0 + +require github.com/prometheus/client_golang v1.18.0 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.15.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/clients/proxy-client/go.sum b/clients/proxy-client/go.sum new file mode 100644 index 0000000..011cf76 --- /dev/null +++ b/clients/proxy-client/go.sum @@ -0,0 +1,26 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/clients/proxy-client/main.go b/clients/proxy-client/main.go new file mode 100644 index 0000000..73dbcff --- /dev/null +++ b/clients/proxy-client/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "context" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + successMetric = prometheus.NewCounter(prometheus.CounterOpts{ + Name: "proxy_check_success", + Help: "Indicates the number of successful proxy checks", + }) +) + +func init() { + prometheus.MustRegister(successMetric) +} + +func checkProxy(ctx context.Context, proxyURL, targetURL string) { + transport, _ := http.DefaultTransport.(*http.Transport) + transport.Proxy = http.ProxyURL(mustParseURL(proxyURL)) + + client := &http.Client{ + Transport: transport, + } + + req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) + if err != nil { + log.Printf("Error creating HTTP request: %s\n", err) + return + } + + resp, err := client.Do(req) + if err != nil { + log.Printf("Error performing HTTP request: %s\n", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + successMetric.Inc() + log.Printf("Proxy check succeed, Healthy status.") + } else { + log.Printf("Proxy check failed. Status code: %d\n", resp.StatusCode) + } +} + +func mustParseURL(rawURL string) *url.URL { + u, err := url.Parse(rawURL) + if err != nil { + panic(err) + } + return u +} + +func main() { + proxyURL := os.Getenv("PROXY_URL") + if proxyURL == "" { + log.Fatal("PROXY_URL environment variable not set") + } + targetURL := "https://ifconfig.me" + + http.Handle("/metrics", promhttp.Handler()) + + server := &http.Server{ + Addr: ":9090", + } + + var wg sync.WaitGroup + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-signalCh + log.Println("Received signal, shutting down gracefully...") + cancel() + server.Shutdown(ctx) + }() + + wg.Add(1) + go func() { + defer wg.Done() + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + log.Println("Ticker routine shutting down...") + return + case <-ticker.C: + checkProxy(ctx, proxyURL, targetURL) + } + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + log.Fatal(server.ListenAndServe()) + }() + + log.Println("Waiting for the HTTP server to finish...") + wg.Wait() + log.Println("All goroutines shut down. Exiting.") +}