spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.82k stars 40.6k forks source link

Spring Boot Maven plugin Kubernetes Integration #31662

Closed salaboy closed 2 years ago

salaboy commented 2 years ago

The Spring Boot Maven plugin is great and using buildpacks for mvn spring-boot:build-image is a great feature, but I would love to see deeper integration with Kubernetes development lifecycles.

I was looking at JKube (I don't like that it uses fabric8 libraries to create the YAML resources)

<plugin>
  <groupId>org.eclipse.jkube</groupId>
  <artifactId>kubernetes-maven-plugin</artifactId>
  <version>1.8.0</version>
</plugin>

Which generates manifests and can be used in conjunction with mvn spring-boot:build-image but still, the integration is not as good as google/ko which is almost 5 years old.

What I will be looking for in a Maven Spring Boot Kubernetes plugin will be to do something like this:

Build, as it is today, but instead of using the Artifact Version, it should use a SHA for tagging the container image, hence consecutive builds will generate a new SHA that can be used to run the container that we just build. I would also include a push to registry function in the build image, as the image locally, in the context of Kubernetes doesn't make much sense. In google/ko this is done by just setting an env variable (export SB_DOCKER_REPO=salaboy) and it will use the docker credentials to push.

mvn spring-boot:build-image // Build image with tag SHA + Push to the container registry by default (can be disabled with a flag)

This command should output in some way the SHA that was just created so it can be piped to another command.. for example:

docker run -p 8080:8080 $(mvn spring-boot:build-image) // Run the container that mvn create with a SHA

Now on the Kubernetes side, because we are creating containers that are tagged with their SHA we can scan Kubernetes manifests and replace where the image that we are building is referenced.

For example, if we have a deployment that looks like this (inside a directory config/ o manifests/): deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service-deployment
  labels:
    app: my-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
        - name: app
          image: salaboy/my-service:0.0.1-SNAPSHOT
          ports:
            - containerPort: 8080

We can use an expression that we can replace for the container that we are building:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service-deployment
  labels:
    app: my-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
        - name: app
          image: sb://org.salaboy:my-service
          ports:
            - containerPort: 8080

Where sb://org.salaboy:my-service makes reference to the artifact that we are building and can be replaced by the container image that we just created which probably will look like: salaboy/my-service-7ddfb3e035b42cd70649cc33393fe32c@sha256:d4f1d0bfa27f13ab7d80969d18eef1883a8bf23591f92c3ca42f91ad761048e1

Running a command mvn spring-boot:k8s-resolve -f config/ should do this replacement in all files inside the directory specified and give you the YAML files to apply to the cluster. Doing this replacement and outputting back YAML files is good for CI scenarios, but we can go a step further and apply this to the cluster by running a command like this:

mvn spring-boot:k8s-apply -f config/

This command should aggregate all the steps described before: Build Container, Push Container, Replace reference in YAML files and kubectl apply -f to the cluster. Because every time we make a change the container image SHA will be different, every time that we run this command should trigger a deployment update to the latest container image that was just pushed.

In contrast with JKube, I wouldn't focus too much on generating manifests but more on the lifecycle of the containers that are built and how new changes are applied to a remote or local cluster.

I am happy to provide more info or details if needed. Apologies for the long issue.

salaboy commented 2 years ago

@philwebb I love the tag for: team-meeting, let me know how that goes I would love to hear feedback about this. Or pointers to alternatives

wilkinsona commented 2 years ago

Thanks for the suggestion, @salaboy. We've discussed this today and agreed that this level of integration with K8S is out of scope for Spring Boot. We feel that it's better handled as part of a software supply chain built with something like Cartographer.

salaboy commented 2 years ago

@wilkinsona while I understand that a decision was made on this issue, it will be great to document the reasons here instead of just pointing to Cartographer which is a specific tool to do way more than what was described here.

IMHO, there is still a gap in the developer experience for spring boot users, that can be fixed by following an approach similar to the one provided by google/ko.

If we are going to delegate tasks like applying the descriptors to a Kubernetes cluster, we should also avoid building container images (using buildpacks that I love) with the spring-boot:build-image goal, as this can also be done by Cartographer and it will be going against that approach as well.

Can you please share the reasons for not building such integration? Is it too much work? Is the outcome or benefits not clear enough? Is a developer not supposed to deploy their containers to Kubernetes for development purposes? I am just trying to understand the Spring Boot team logic behind the decision.

wilkinsona commented 2 years ago

Our build pack support is intended as an on-ramp to tools like pack, kpack and the like and we believe it fulfils that purpose in its current form.

As I said above, we do not want to build further integration in Spring Boot itself as we believe it to be out of scope. The benefits are clear but it's not our place to provide them.