diff --git a/README.md b/README.md index 28858db..1f2ea3f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ devkube bootstraps feature-rich Kubernetes clusters locally using Docker or on a Batteries included +- [Registry](https://github.com/distribution/distribution) - image distribution - [Dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/) - web-based user interface - [Cert-Manager](https://cert-manager.io)- certificate management - [Grafana](https://grafana.com/grafana/) - data observability diff --git a/app/cluster/cluster_create.go b/app/cluster/cluster_create.go index c74921f..715d84f 100644 --- a/app/cluster/cluster_create.go +++ b/app/cluster/cluster_create.go @@ -13,6 +13,7 @@ import ( "github.com/adrianliechti/devkube/extension/dashboard" "github.com/adrianliechti/devkube/extension/metrics" "github.com/adrianliechti/devkube/extension/observability" + "github.com/adrianliechti/devkube/extension/registry" ) func CreateCommand() *cli.Command { @@ -78,6 +79,10 @@ func CreateCommand() *cli.Command { return err } + if err := registry.Install(c.Context, kubeconfig, DefaultNamespace); err != nil { + return err + } + if err := observability.Install(c.Context, kubeconfig, DefaultNamespace); err != nil { return err } diff --git a/app/cluster/cluster_registry.go b/app/cluster/cluster_registry.go new file mode 100644 index 0000000..701682a --- /dev/null +++ b/app/cluster/cluster_registry.go @@ -0,0 +1,74 @@ +package cluster + +import ( + "fmt" + "runtime" + + "github.com/adrianliechti/devkube/app" + "github.com/adrianliechti/devkube/pkg/cli" + "github.com/adrianliechti/devkube/pkg/kubectl" +) + +func RegistryCommand() *cli.Command { + return &cli.Command{ + Name: "registry", + Usage: "Connect Registry", + + Flags: []cli.Flag{ + app.ProviderFlag, + app.ClusterFlag, + app.PortFlag, + }, + + Before: func(c *cli.Context) error { + if _, _, err := kubectl.Info(c.Context); err != nil { + return err + } + + return nil + }, + + Action: func(c *cli.Context) error { + provider, cluster := app.MustCluster(c) + + kubeconfig, closer := app.MustClusterKubeconfig(c, provider, cluster) + defer closer() + + port := 5000 + + if runtime.GOOS == "darwin" { + port = 5001 + } + + port = app.MustPortOrRandom(c, port) + + cli.Info("Configure Docker to use this registry") + cli.Info(" {") + cli.Info(" ...") + cli.Info(" \"insecure-registries\": [") + cli.Infof(" \"localhost:%d\",", port) + cli.Infof(" \"host.docker.internal:%d\"", port) + cli.Info(" ]") + cli.Info(" ...") + cli.Info(" }") + cli.Info(" (see https://docs.docker.com/registry/insecure/#deploy-a-plain-http-registry)") + cli.Info() + cli.Info() + cli.Info("Push an image") + cli.Infof(" docker tag my-image host.docker.internal:%d/my-image", port) + cli.Infof(" docker push host.docker.internal:%d/my-image", port) + cli.Info() + cli.Info("Push an image (not using BuildKit)") + cli.Infof(" docker tag my-image localhost:%d/my-image", port) + cli.Infof(" docker push localhost:%d/my-image", port) + cli.Info() + cli.Info() + + if err := kubectl.Invoke(c.Context, []string{"port-forward", "service/registry", fmt.Sprintf("%d:80", port)}, kubectl.WithKubeconfig(kubeconfig), kubectl.WithNamespace(DefaultNamespace), kubectl.WithDefaultOutput()); err != nil { + return err + } + + return nil + }, + } +} diff --git a/extension/registry/registry.go b/extension/registry/registry.go new file mode 100644 index 0000000..73107f4 --- /dev/null +++ b/extension/registry/registry.go @@ -0,0 +1,30 @@ +package registry + +import ( + "context" + _ "embed" + "strings" + + "github.com/adrianliechti/devkube/pkg/kubectl" +) + +var ( + //go:embed registry.yaml + manifest string +) + +func Install(ctx context.Context, kubeconfig, namespace string) error { + if namespace == "" { + namespace = "default" + } + + return kubectl.Invoke(ctx, []string{"apply", "-f", "-"}, kubectl.WithKubeconfig(kubeconfig), kubectl.WithNamespace(namespace), kubectl.WithInput(strings.NewReader(manifest)), kubectl.WithDefaultOutput()) +} + +func Uninstall(ctx context.Context, kubeconfig, namespace string) error { + if namespace == "" { + namespace = "default" + } + + return kubectl.Invoke(ctx, []string{"delete", "-f", "-"}, kubectl.WithKubeconfig(kubeconfig), kubectl.WithNamespace(namespace), kubectl.WithInput(strings.NewReader(manifest)), kubectl.WithDefaultOutput()) +} diff --git a/extension/registry/registry.yaml b/extension/registry/registry.yaml new file mode 100644 index 0000000..5ff6c42 --- /dev/null +++ b/extension/registry/registry.yaml @@ -0,0 +1,152 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: registry + labels: + app: registry +spec: + secretName: registry-tls + dnsNames: + - registry.loop + - registry + - localhost + issuerRef: + name: platform + kind: Issuer + group: cert-manager.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: registry + labels: + app: registry +spec: + selector: + matchLabels: + app: registry + template: + metadata: + labels: + app: registry + spec: + containers: + - name: registry + image: registry:2 + env: + - name: REGISTRY_HTTP_ADDR + value: 0.0.0.0:80 + ports: + - containerPort: 80 + volumeMounts: + - name: data + mountPath: /var/lib/registry + resources: {} + - name: proxy + image: adrianliechti/loop-proxy + args: + - "-port" + - "443" + - "-target" + - "http://localhost" + - "-key-file" + - "certs/tls.key" + - "-cert-file" + - "certs/tls.crt" + ports: + - containerPort: 443 + volumeMounts: + - name: certs + mountPath: "/app/certs" + resources: {} + volumes: + - name: data + persistentVolumeClaim: + claimName: registry + - name: certs + secret: + secretName: registry-tls +--- +apiVersion: v1 +kind: Service +metadata: + name: registry + labels: + app: registry +spec: + type: ClusterIP + selector: + app: registry + ports: + - name: http + port: 80 + targetPort: 80 + - name: https + port: 443 + targetPort: 443 +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: registry + labels: + app: registry +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: registry-proxy + labels: + app: registry-proxy +spec: + selector: + matchLabels: + app: registry-proxy + template: + metadata: + labels: + app: registry-proxy + spec: + hostPID: true + initContainers: + - name: init + image: busybox + securityContext: + privileged: true + command: + - nsenter + - --mount=/proc/1/ns/mnt + - -- + - sh + - -c + - "grep -q 'registry' /etc/hosts || echo 127.88.0.10 registry registry.loop >> /etc/hosts" + containers: + - name: proxy + image: adrianliechti/loop-proxy + args: + - "-port" + - "443" + - "-target" + - "http://registry" + - "-key-file" + - "certs/tls.key" + - "-cert-file" + - "certs/tls.crt" + ports: + - containerPort: 443 + hostIP: 127.88.0.10 + hostPort: 443 + volumeMounts: + - name: certs + mountPath: "/app/certs" + resources: {} + volumes: + - name: certs + secret: + secretName: registry-tls diff --git a/main.go b/main.go index 1585dbc..d15a7bb 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,9 @@ func initApp() cli.App { cluster.CreateCommand(), cluster.DeleteCommand(), + cluster.SetupCommand(), + cluster.RegistryCommand(), cluster.GrafanaCommand(), cluster.DashboardCommand(),