-
Notifications
You must be signed in to change notification settings - Fork 350
Generate and maintain resolv.conf for sandbox #50
Conversation
Will take a look tomorrow, and let's try to get it merged this week. |
pkg/server/sandbox_run.go
Outdated
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox. | ||
// Set DNS options. | ||
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil { | ||
// resolvPath is not "" guaranteed by 'dnsConfig != nil' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should generate resolve file in RunPodSandbox
.
And only AddBindMount
in generateSandboxContainerSpec
.
I want to make generateSandboxContainerSpec
only simply convert CRI config into OCI spec, so that we could reuse it or consolidate it with cri-o in the future.
pkg/server/sandbox_run.go
Outdated
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil { | ||
// resolvPath is not "" guaranteed by 'dnsConfig != nil' | ||
resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config) | ||
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate parse
logic and write logic, which makes it easier to write unit test, and also makes the code more clear.
pkg/server/sandbox_run.go
Outdated
resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config) | ||
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath) | ||
if err != nil { | ||
err1 := removeFile(resolvPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to add a new function, use RemoveAll
which doesn't report error when file doesn't exist.
pkg/server/sandbox_run.go
Outdated
resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config) | ||
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath) | ||
if err != nil { | ||
err1 := removeFile(resolvPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use defer to cleanup file, or else if there is error returned later in this function, you will not cleanup the file.
And another reason we should create the file in RunPodSandbox
is that we should cleanup the file whenever there is an error in RunPodSandbox
.
In your current implementation, if there is an error create the container, RunPodSandbox
will return an error, but the resolve.conf
file will not be cleaned up, which causes unnecessary file leakage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that we've already had code cleaning up the sandbox root. https://github.com/kubernetes-incubator/cri-containerd/blob/master/pkg/server/sandbox_run.go#L112
We can rely on that to cleanup.
pkg/server/sandbox_run.go
Outdated
resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config) | ||
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath) | ||
if err != nil { | ||
err1 := removeFile(resolvPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Log the error in defer.
pkg/server/container_start_test.go
Outdated
testPid := uint32(1234) | ||
config, sandboxConfig, specCheck := getStartContainerTestData() | ||
c := newTestCRIContainerdService() | ||
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig) | ||
spec, err := c.generateContainerSpec(testID, testPodID, testPid, config, sandboxConfig) | ||
assert.NoError(t, err) | ||
specCheck(t, testID, testPid, spec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass testSandboxID
in, and check whether resolv.conf
is mounted.
pkg/server/sandbox_run.go
Outdated
@@ -225,7 +228,20 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r | |||
// Set hostname. | |||
g.SetHostname(config.GetHostname()) | |||
|
|||
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox. | |||
// Set DNS options. | |||
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even when dnsConfig
== nil, we should use the copy of resolve.conf
on the host.
That should be the default behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this is my original thought, not sure why cri-o guys do this assumption...
pkg/server/sandbox_run.go
Outdated
@@ -225,7 +228,20 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r | |||
// Set hostname. | |||
g.SetHostname(config.GetHostname()) | |||
|
|||
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should remove resolve.conf
in RemovePodSandbox
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVM, the whole sandbox root directory will be removed.
pkg/server/container_start.go
Outdated
// TODO(random-liu): [P0] Bind mount sandbox resolv.conf. | ||
// Bind mount sandbox resolv.conf. | ||
if resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, sandboxID), sandboxConfig); resolvPath != "" { | ||
g.AddBindMount(resolvPath, resolvConfPath, []string{"ro"}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's mount it as "rw"/"ro" based on securityContext.GetReadonlyRootfs()
for now (Docker's behavior today), and add TODO to figure out whether we should always mount it as read-only.
Let's avoid regression for now, there may be user relying on this behavior, although it's not encouraged.
pkg/server/sandbox_run_test.go
Outdated
@@ -113,7 +113,8 @@ func TestGenerateSandboxContainerSpec(t *testing.T) { | |||
if test.configChange != nil { | |||
test.configChange(config) | |||
} | |||
spec := c.generateSandboxContainerSpec(testID, config) | |||
spec, err := c.generateSandboxContainerSpec(testID, config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test resolve.conf bind mount.
@Crazykev Please rebase your PR and address comments |
@Random-Liu Addressed comments and added related unit test. PTAL. |
pkg/server/sandbox_run.go
Outdated
resolvPath := getResolvPath(sandboxRootDir) | ||
if len(resolvContent) == 0 && hostNetwork { | ||
// copy host's resolv.conf to resolvPath | ||
// err = c.os.CopyFile(resolvConfPath, resolvPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will uncomment this after #60 merged.
7c6bccb
to
5668536
Compare
@Random-Liu Rebased. |
pkg/os/os.go
Outdated
@@ -75,3 +77,9 @@ func (RealOS) CopyFile(src, dest string, perm os.FileMode) error { | |||
_, err = io.Copy(out, in) | |||
return err | |||
} | |||
|
|||
// WriteFile writes data to a file named by filename. If the file does not exist, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: WriteFile will call ioutil.WriteFile to write data into a file.
The details comment of the function is already covered by golang doc.
pkg/os/testing/fake_os.go
Outdated
@@ -142,3 +143,15 @@ func (f *FakeOS) CopyFile(src, dest string, perm os.FileMode) error { | |||
} | |||
return nil | |||
} | |||
|
|||
// WriteFile is a fake call that invokes WriteFile or just return nil. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/WriteFile/WrteFileFn.
pkg/server/container_start.go
Outdated
|
||
// TODO(random-liu): [P2] Add apparmor and seccomp. | ||
|
||
// TODO(random-liu): [P1] Bind mount sandbox /dev/shm. | ||
|
||
// TODO(random-liu): [P0] Bind mount sandbox resolv.conf. | ||
// Bind mount sandbox resolv.conf. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add this logic into generateContainerMounts
?
And remove the corresponding TODO in generateContainerMounts
.
pkg/server/container_start.go
Outdated
// TODO(random-liu): [P0] Bind mount sandbox resolv.conf. | ||
// Bind mount sandbox resolv.conf. | ||
resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, sandboxID)) | ||
// TODO Need to figure out whether we should always mount it as read-only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/TODO/TODO:
pkg/server/helpers.go
Outdated
@@ -393,3 +398,8 @@ func (c *criContainerdService) ensureImageExists(ctx context.Context, ref string | |||
} | |||
return meta, nil | |||
} | |||
|
|||
// getResolvPath returns resolv.conf filepath for specified sandbox. | |||
func getResolvPath(sandboxRoot string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Move this up to around getSandboxHosts
.
pkg/server/sandbox_run.go
Outdated
// copy host's resolv.conf to resolvPath | ||
err = c.os.CopyFile(resolvConfPath, resolvPath, 0755) | ||
} else { | ||
err = c.os.WriteFile(resolvPath, resolvContent, 0755) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, should be 0644
.
pkg/server/sandbox_run.go
Outdated
err = c.os.WriteFile(resolvPath, resolvContent, 0755) | ||
} | ||
if err != nil { | ||
return fmt.Errorf("failed to copy/write file to %q: %v", resolvPath, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's return err separately.
pkg/server/sandbox_run.go
Outdated
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil { | ||
resolvContent, err = parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse sandbox dnsConfig: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fmt.Errorf("failed to parse sandbox DNSConfig %+v: %v", DNSConfig, err)
pkg/server/sandbox_run_test.go
Outdated
servers: []string{"8.8.8.8", "server.google.com"}, | ||
searches: []string{"114.114.114.114"}, | ||
options: []string{"timeout:1"}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Let's add test case to check whether the function returns error when searches is more than 6
- Let's add
expectedOutput
into the test case, and directly checkassert.Equal(t, expectedOutput, resolvContent)
, e.g.:
map[string]struct{
dnsConfig *runtime.DNSConfig
expectedContent string
expectErr bool
} {
"empty dns options should return empty content": {
dnsConfig: &runtime.DNSConfig{},
expectedOutput: "",
},
"non-empty dns options should return correct content": {
dnsConfig: &runtime.DNSConfig{/*...*/},
expectedOutput: `search 114.114.114.114
nameserver 8.8.8.8
nameserver server.google.com
...
`
},
"should return error if dns search exceeds limit": {
/*...*/
},
}
pkg/server/sandbox_run.go
Outdated
|
||
// parseDNSOptions parse DNS options into resolv.conf format content, | ||
// if none option is specified, will return empty with no error. | ||
func parseDNSOptions(servers, searches, options []string) ([]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we use string
in this function, and use resolvContent += fmt.Sprintf("search %s\n", strings.Join(searches, " "))
, which is much more cleaner.
@Crazykev Reviewed. Nits are nice to address. Others please take a look. |
e1f793c
to
0ab02d0
Compare
@Random-Liu Updated, PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One change request and several nits, otherwise LGTM.
pkg/server/container_start.go
Outdated
// TODO: Need to figure out whether we should always mount it as read-only | ||
mounts = append(mounts, &runtime.Mount{ | ||
ContainerPath: resolvConfPath, | ||
HostPath: resolvPath, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: getResolvPath(sandboxRootDir)
to save one line.
pkg/server/container_start_test.go
Outdated
}, | ||
{ | ||
ContainerPath: resolvConfPath, | ||
HostPath: getResolvPath(testSandboxRootDir), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Use testSandboxRootDir + "/resolv.conf"
, because we don't know whether getResolvPath
works or not, which is also what we want to test.
pkg/server/sandbox_run.go
Outdated
} | ||
resolvPath := getResolvPath(rootDir) | ||
hostNetwork := config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork() | ||
if resolvContent == "" && hostNetwork { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/dockershim/docker_sandbox.go#L98-L103 and https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/dockershim/docker_sandbox.go#L616-L651, we should overwrite resolv.conf
no matter it's using hostNetwork or not as long as resolvContent != ""
.
Should be:
if resolvContent == "" {
// CopyFile
} else {
// WriteFile
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should overwrite resolv.conf no matter it's using hostNetwork or not as long as resolvContent != "".
Yep, we should. While if it's not hostNetwork and resolvContent is empty(DnsConfig is nil or content is empty), should we just generate a empty resolv.conf(maybe one with default setting?) for this pod? In your logic it like we should pass host's resolv.conf to pod in this scene.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Crazykev Based on current dockershim implementation, we only rewrite when resolvContent
is not empty. Let's keep the same behavior now.
pkg/server/sandbox_run.go
Outdated
} | ||
resolvPath := getResolvPath(rootDir) | ||
hostNetwork := config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork() | ||
if resolvContent == "" && hostNetwork { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we also need unit test for this function. However, I'm fine with leave it for now. I'll add it in #67.
pkg/server/sandbox_run.go
Outdated
nSearches := len(searches) | ||
nOptions := len(options) | ||
resolvContent := "" | ||
if nServers == 0 && nSearches == 0 && nOptions == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This seems unnecessary, the function will return empty string anyway if all are empty.
And if we get rid of this block, we could directly use len
in the following if clause, because we only need the result once.
@Random-Liu Addressed comments except adding a new test case, left it to you ;-) |
Signed-off-by: Crazykev <crazykev@zju.edu.cn>
Signed-off-by: Crazykev <crazykev@zju.edu.cn>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one nit. Will just apply the label and merge.
@Crazykev Thanks a lot!
}, | ||
{ | ||
ContainerPath: resolvConfPath, | ||
HostPath: getResolvPath(testSandboxRootDir), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This also needs to be updated, but I could do it in my PR.
Generate and maintain resolv.conf for sandbox
This is implement part of #28, I'll continue to write some related unit test for this.
@Random-Liu I copy a host resolv.conf for each pod with hostNetwork, since user may change this file in container. Let me know if you change your mind.
Signed-off-by: Crazykev crazykev@zju.edu.cn