Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use distroless image to build controller and speaker containers #2418

Merged
merged 1 commit into from
Jun 12, 2024

Conversation

minmzzhang
Copy link

@minmzzhang minmzzhang commented Jun 3, 2024

Inspired by #2117
fixes #2110

A big thank you to @oribon and @georgettica for the detail discussion in #2117 which is the design document I followed to this solution. Also appreciate @oribon 's help during design implementation, that I was able to run local integration and e2e tests!

Is this a BUG FIX or a FEATURE ?:

/kind feature

What this PR does / why we need it:
gcr.io/distroless/static base image is used to replace the original docker.io/alpine image, for a distroless solution that controller and speaker images will be built from scratch. Since distroless base image has no shell entry, there are two major impacts with this solution:

  1. Some of the frr init containers which need to move files around will need a local copy tool.
  2. Some of the e2e tests which need to execute certain commands (e.g. "ifconfig") from inside the speaker will need to have an ephemeral container attached to the pod to complete the task.

In this PR, a local copy tool (cp-tool) was introduced to cope with the first concern, and a podDebugExecutor was introduced under e2etest to attach a debug ephemeral container when there is a need to execute additional commands.

Special notes for your reviewer:

Release note:

The speaker and the controller containers are now based on a distroless image, reducing the attack surface

@georgettica
Copy link

Also, the cp-tool is a good addition to solve this. Thank you!

One thing I thought of that might happen is if you debug twice on the same pod. It'll fail I reckon.

Not that I think it'll happen now, but just adding here if someone hits this bug and wants to resolve.

Amazing work and I thank you deeply for making the efforts we had come to a reality, I am truly glad

@minmzzhang minmzzhang force-pushed the distroless-try branch 9 times, most recently from a04d45e to e7746b0 Compare June 4, 2024 14:24
Copy link
Member

@oribon oribon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome work! thanks a lot
can you also squash your commits into one with the gist of the change?(https://metallb.io/community/#commit-messages)

if Kubectl == "" {
return "", errors.New("the kubectl parameter is not set")
}
fullargs := append([]string{"debug", "-it", "-n", pd.namespace, pd.name, "-c", "pod-executor-debugger", "--image=busybox", "--", cmd}, args...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should randomize the container name here pod-executor-debugger-.., the reason being that if you run the same debug container multiple times it only remembers the first output (the first debug command that caused the container to be terminated) which could cause confusion if the test is ran multiple times on the same cluster

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A randomized string (length=5) was added to the pod executor debugger.

// add ephemeral container to deal with distroless image
type podDebugExecutor struct {
namespace string
name string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about addibg an image for the container here (instead of defaulting to busybox)? this will make the call for ForPodDebug more resemblant to the usual kubectl debug where the caller specifies the image.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the randomized string for debugger.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image name is configurable now.

sourceFile = filepath.Clean(sourceFile)
destinationFile = filepath.Clean(destinationFile)

err = cp.Copy(sourceFile, destinationFile)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you try something that doesn't require importing another dependency? I wonder if we can just do this with some io.Copy (or anything else from the standard library) that wouldn't be too messy since our usecase is pretty simple

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally there was file permission issue for the local copy tool when using io.Copy, so I switched to a community package. With the new submission, I've added the preserve permission functionality, so it should be clean now.

)

func main() {
logger, err := logging.Init("error")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't really need a logger here, let's just panic / fmt.Printf on the few occasions where needed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger removed.

@minmzzhang minmzzhang force-pushed the distroless-try branch 4 times, most recently from 755b9b4 to 4878b57 Compare June 6, 2024 14:45
Copy link
Member

@oribon oribon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left a few small comments, looks really good!
also, please change the commit message to be more about "what is the change" and not "how we changed" :)
approved, cc @fedepaol to also give this a look

Comment on lines 69 to 70
sourceFile = filepath.Clean(sourceFile)
destinationFile = filepath.Clean(destinationFile)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: this is probably redundant with the one happening inside the function

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the redundant part.

Comment on lines 12 to 14
func CopyFile(srcFile string, destFile string) error {
srcFile = filepath.Clean(srcFile)
destFile = filepath.Clean(destFile)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename the input to src,dst (so the new vars are declared with new names)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed the inputs

}

srcPerm := srcStat.Mode().Perm()
fmt.Printf("File permission before copying %v \n", srcPerm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we can remove it (unless you think this adds value)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed after checking the functionality.

Comment on lines 50 to 57
dstStat, err := os.Stat(destFile)
if err != nil {
err = fmt.Errorf("failed to check stats for %s: %w", destFile, err)
return err
}

dstPerm := dstStat.Mode().Perm()
fmt.Printf("File permission After copying %v \n", dstPerm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed after checking the functionality.

@minmzzhang minmzzhang force-pushed the distroless-try branch 2 times, most recently from 34d7943 to 7370af4 Compare June 10, 2024 14:35
return "", errors.New("the kubectl parameter is not set")
}

imagearg := "--image=" + pd.image
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: imageArg, targetArg, debuggerArg

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

@@ -170,7 +170,7 @@ var _ = ginkgo.Describe("L2-interface selector", func() {
}, 1*time.Minute, 1*time.Second).Should(Not(HaveOccurred()))
speakerPod, err := metallb.SpeakerPodInNode(cs, svcNode.Name)
Expect(err).NotTo(HaveOccurred())
selectorMac, err := mac.GetIfaceMac(NodeNics[0], executor.ForPod(speakerPod.Namespace, speakerPod.Name, "speaker"))
selectorMac, err := mac.GetIfaceMac(NodeNics[0], executor.ForPodDebug(speakerPod.Namespace, speakerPod.Name, "speaker", "busybox"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only concern I have here is relying on another image (from dockerhub, also), which may result in hitting the pull limits. How about using the agnhost image we are already using here (assuming it already has all we need to debug)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! changed.

@@ -288,3 +288,11 @@ Additionally, the status of the service and of the endpoints must be provided:
kubectl get endpointslices <my-service> -o yaml
kubectl get svc <my_service> -o yaml
```

### How to debug a container with distroless image
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### How to debug a container with distroless image
### How to debug the speaker and the controller containers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed..


### How to debug a container with distroless image

With the distroless solution, if someone needs to debug inside speaker container, an ephemeral container should be used:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
With the distroless solution, if someone needs to debug inside speaker container, an ephemeral container should be used:
Due to the fact that both the speaker and the controller containers are based on a distroless image, an ephemeral container should be used to debug inside them:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modified.

@fedepaol
Copy link
Member

Also, I'd reword the release notes section, saying that now the speaker and the controller are based on distroless.

Signed-off-by: Min Zhang <minzhang@redhat.com>
@fedepaol
Copy link
Member

LGTM

@fedepaol fedepaol enabled auto-merge June 12, 2024 18:50
@fedepaol fedepaol added this pull request to the merge queue Jun 12, 2024
Merged via the queue into metallb:main with commit 66d8395 Jun 12, 2024
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use distroless images for the speaker and the controller
5 participants