Skip to content

Commit

Permalink
prepare deployment (#20)
Browse files Browse the repository at this point in the history
* switch to connectrpc from grpc-go

Signed-off-by: Artem Bortnikov <[email protected]>

* machine service endpoints

Signed-off-by: Artem Bortnikov <[email protected]>

* machinetype service endpoints

Signed-off-by: Artem Bortnikov <[email protected]>

* fix timestamps

Signed-off-by: Artem Bortnikov <[email protected]>

* update tests

Signed-off-by: Artem Bortnikov <[email protected]>

* cleanup

Signed-off-by: Artem Bortnikov <[email protected]>

* deployment

Signed-off-by: Artem Bortnikov <[email protected]>

* update logging

Signed-off-by: Artem Bortnikov <[email protected]>

* linter

Signed-off-by: Artem Bortnikov <[email protected]>

* update dockerfiles

Signed-off-by: Artem Bortnikov <[email protected]>

---------

Signed-off-by: Artem Bortnikov <[email protected]>
  • Loading branch information
aobort authored Mar 6, 2024
1 parent 7253fa4 commit e2f2996
Show file tree
Hide file tree
Showing 74 changed files with 3,211 additions and 2,381 deletions.
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Build the manager binary
FROM golang:1.21 as builder
ARG TARGETOS
ARG TARGETARCH
FROM golang:1.22.0 as builder

ARG GOARCH

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer

RUN go mod download

# Copy the go source
COPY cmd/main.go cmd/main.go
COPY cmd/lifecycle-controller-manager/main.go cmd/lifecycle-controller-manager/main.go
COPY api/ api/
COPY lcmi/ lcmi/
COPY internal/ internal/
COPY util/ util/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build -a -o manager cmd/lifecycle-controller-manager/main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
Expand Down
43 changes: 42 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
IMG ?= controller:latest
DOCKERFILE ?= .

.PHONY: fmt
fmt: goimports
go fmt ./...
Expand Down Expand Up @@ -51,6 +54,42 @@ format: generate manifests add-license fmt lint-fix
docs: gen-crd-api-reference-docs ## Run go generate to generate API reference documentation.
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir ./api/lifecycle/v1alpha1 -config ./hack/api-reference/config.json -template-dir ./hack/api-reference/template -out-file ./docs/api-reference/lifecycle.md

### BUILD IMAGES ###
.PHONY: docker-build-controller-manager
docker-build: ## Build docker image with the manager.
docker build . -t ${IMG}

.PHONY: docker-build-lifecycle-service
docker-build-lcmi: ## Build docker image with the manager.
docker build . -t ${IMG} -f ${DOCKERFILE}

### INSTALL AND DEPLOY ###
.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl apply -f -

.PHONY: uninstall
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl delete -f -

.PHONY: deploy-controller-manager
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -

.PHONY: undeploy-controller-manager
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/default | kubectl delete -f -

.PHONY: deploy-lifecycle-service
deploy-lcmi: kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/lcmi/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/lcmi/default | kubectl apply -f -

.PHONY: undeploy-lifecycle-service
undeploy-lcmi: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/lcmi/default | kubectl delete -f -

### AUXILIARY ###
LOCAL_BIN ?= $(shell pwd)/bin
$(LOCAL_BIN):
Expand All @@ -74,6 +113,7 @@ MODELS_SCHEMA ?= $(LOCAL_BIN)/models-schema
VGOPATH ?= $(LOCAL_BIN)/vgopath
GEN_CRD_API_REFERENCE_DOCS ?= $(LOCAL_BIN)/gen-crd-api-reference-docs
BUF ?= $(LOCAL_BIN)/buf
KUSTOMIZE ?= $(LOCAL_BIN)/kustomize

## Tools versions
ADDLICENSE_VERSION ?= v1.1.1
Expand All @@ -86,6 +126,7 @@ VGOPATH_VERSION ?= v0.1.3
GEN_CRD_API_REFERENCE_DOCS_VERSION ?= v0.3.0
MODELS_SCHEMA_VERSION ?= main
BUF_VERSION ?= v1.29.0
KUSTOMIZE_VERSION ?= v5.3.0

.PHONY: code-gen
code-gen: vgopath deepcopy-gen models-schema openapi-gen applyconfiguration-gen client-gen
Expand Down Expand Up @@ -168,7 +209,7 @@ $(CLIENT_GEN): $(LOCAL_BIN)
.PHONY: kustomize
kustomize: $(KUSTOMIZE)
$(KUSTOMIZE): $(LOCAL_BIN)
@test -s $(KUSTOMIZE) || GOBIN=$(LOCAL_BIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION)
@test -s $(KUSTOMIZE) || GOBIN=$(LOCAL_BIN) go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION)

.PHONY: buf
buf: $(BUF)
Expand Down
1 change: 1 addition & 0 deletions api/lifecycle/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type PackageVersion struct {
type ScanResult string

const (
Unspecified ScanResult = ""
ScanFailure ScanResult = "Failure"
ScanSuccess ScanResult = "Success"
)
Expand Down
1 change: 1 addition & 0 deletions api/lifecycle/v1alpha1/machine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type MachineStatus struct {
Message string `json:"message"`

// Conditions reflects Machine conditions and their state
// +kubebuilder:validation:Optional
Conditions []metav1.Condition `json:"conditions"`
}

Expand Down
1 change: 0 additions & 1 deletion api/lifecycle/v1alpha1/machinetype_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ type MachineTypeStatus struct {

// LastScanResult reflects the result of the last scan.
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=Success;Failure
LastScanResult ScanResult `json:"lastScanResult"`

// AvailablePackages reflects the list of AvailablePackageVersion
Expand Down
2 changes: 1 addition & 1 deletion clientgo/openapi/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 44 additions & 8 deletions cmd/lifecycle-controller-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
package main

import (
"context"
"crypto/tls"
"flag"
"net"
"net/http"
"os"
"time"

"connectrpc.com/connect"
"github.com/ironcore-dev/lifecycle-manager/lcmi/api/machine/v1alpha1/machinev1alpha1connect"
"github.com/ironcore-dev/lifecycle-manager/lcmi/api/machinetype/v1alpha1/machinetypev1alpha1connect"
oobv1alpha1 "github.com/ironcore-dev/oob/api/v1alpha1"
"golang.org/x/net/http2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/healthz"

Expand All @@ -30,18 +39,23 @@ import (
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")

lcmiEndpoint = "http://lifecycle-service-svc:8080"
)

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(lifecyclev1alpha1.AddToScheme(scheme))
utilruntime.Must(oobv1alpha1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}

func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var lcmiServiceAddr string
flag.StringVar(&lcmiServiceAddr, "lcmi-address", lcmiEndpoint, "The address lifecycle-service running on.")
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
Expand All @@ -67,7 +81,7 @@ func main() {
os.Exit(1)
}

if err = setupControllers(mgr); err != nil {
if err = setupControllers(mgr, lcmiServiceAddr); err != nil {
os.Exit(1)
}
if err = setupHandlers(mgr); err != nil {
Expand All @@ -81,19 +95,22 @@ func main() {
}
}

func setupControllers(mgr ctrl.Manager) error {
func setupControllers(mgr ctrl.Manager, endpoint string) error {
httpClient := setupHTTPClient()
if err := (&controllers.MachineReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: mgr.GetLogger().WithName("lifecycle-machine-controller"),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: mgr.GetLogger().WithName("lifecycle-machine-controller"),
MachineServiceClient: setupMachineClient(endpoint, httpClient),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Machine")
return err
}
if err := (&controllers.MachineTypeReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: mgr.GetLogger().WithName("lifecycle-machinetype-controller"),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: mgr.GetLogger().WithName("lifecycle-machinetype-controller"),
MachineTypeServiceClient: setupMachineTypeClient(endpoint, httpClient),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MachineType")
return err
Expand Down Expand Up @@ -123,3 +140,22 @@ func setupHandlers(mgr ctrl.Manager) error {
}
return nil
}

func setupHTTPClient() *http.Client {
return &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(_ context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
}
}

func setupMachineClient(endpoint string, cl *http.Client) machinev1alpha1connect.MachineServiceClient {
return machinev1alpha1connect.NewMachineServiceClient(cl, endpoint, connect.WithGRPC())
}

func setupMachineTypeClient(endpoint string, cl *http.Client) machinetypev1alpha1connect.MachineTypeServiceClient {
return machinetypev1alpha1connect.NewMachineTypeServiceClient(cl, endpoint, connect.WithGRPC())
}
35 changes: 35 additions & 0 deletions cmd/lifecycle-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Build the manager binary
FROM golang:1.22.0 as builder

ARG GOARCH

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum

RUN go mod download

# Copy the go source
COPY cmd/lifecycle-service/ cmd/lifecycle-service
COPY api/ api/
COPY clientgo/applyconfiguration clientgo/applyconfiguration
COPY clientgo/lifecycle clientgo/lifecycle
COPY lcmi/ lcmi/
COPY util/ util/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build -a -o manager cmd/lifecycle-service/main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]
24 changes: 16 additions & 8 deletions cmd/lifecycle-service/app/app.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
Expand All @@ -6,7 +9,7 @@ import (
"os"
"time"

"github.com/ironcore-dev/lifecycle-manager/lcmi/server"
"github.com/ironcore-dev/lifecycle-manager/lcmi/svc"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"sigs.k8s.io/controller-runtime/pkg/client/config"
Expand All @@ -30,20 +33,24 @@ type Options struct {
kubeconfig string
logLevel string
logFormat string
host string
port int
namespace string
scanPeriod time.Duration
horizon time.Duration
dev bool
}

func (o *Options) addFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.kubeconfig, "kubeconfig", "", "path to kubeconfig file")
fs.StringVar(&o.logLevel, "log-level", "info", "logging level")
fs.StringVar(&o.logFormat, "log-format", "json", "logging format")
fs.IntVar(&o.port, "port", 26500, "bind port")
fs.StringVar(&o.host, "host", "", "bind host")
fs.IntVar(&o.port, "port", 8080, "bind port")
fs.StringVar(&o.namespace, "namespace", "default", "default namespace name")
fs.DurationVar(&o.scanPeriod, "scan-period", time.Hour*24, "scan period")
fs.DurationVar(&o.horizon, "horizon", time.Minute*30, "allowed lag for scan period check")
fs.BoolVar(&o.dev, "dev", false, "development mode flag")
}

func Command() *cobra.Command {
Expand All @@ -70,28 +77,29 @@ func Run(ctx context.Context, opts Options) error {
return err
}

srvOpts := server.Options{
srvOpts := svc.Options{
Cfg: cfg,
Log: setupLogger(LogFormat(opts.logFormat), logLevelMapping[opts.logLevel]),
Log: setupLogger(LogFormat(opts.logFormat), logLevelMapping[opts.logLevel], opts.dev),
Host: opts.host,
Port: opts.port,
Namespace: opts.namespace,
ScanPeriod: opts.scanPeriod,
Horizon: opts.horizon,
}
srv := server.NewLifecycleGRPCServer(srvOpts)
srv := svc.NewGrpcServer(srvOpts)
return srv.Start(ctx)
}

func setupLogger(format LogFormat, level slog.Leveler) *slog.Logger {
func setupLogger(format LogFormat, level slog.Leveler, dev bool) *slog.Logger {
switch format {
case JSON:
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
AddSource: dev,
Level: level,
}))
case Text:
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
AddSource: dev,
Level: level,
}))
}
Expand Down
32 changes: 32 additions & 0 deletions cmd/lifecycle-service/app/signal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package app

import (
"context"
"os"
"os/signal"
"syscall"
)

var (
shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
onlyOneSignalHandler = make(chan struct{})
)

// SetupSignalHandler registers for SIGTERM and SIGINT. A context is returned
// which is canceled on one of these signals.
func SetupSignalHandler() context.Context {
close(onlyOneSignalHandler) // panics when called twice

ctx, cancel := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, shutdownSignals...)
go func() {
<-c
cancel()
}()

return ctx
}
Loading

0 comments on commit e2f2996

Please sign in to comment.