Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Generate and maintain resolv.conf for sandbox #50

Merged
merged 2 commits into from
Jun 9, 2017

Conversation

Crazykev
Copy link
Contributor

This is implement part of #28, I'll continue to write some related unit test for this.

And we may need to maintain it for hostNetwork pod, let me double check it and rely later.

@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

@Random-Liu
Copy link
Member

Will take a look tomorrow, and let's try to get it merged this week.

@Random-Liu Random-Liu added this to the v0.1.0-rc2 milestone May 30, 2017
@Random-Liu Random-Liu self-assigned this May 31, 2017
// 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'
Copy link
Member

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.

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)
Copy link
Member

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.

resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config)
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath)
if err != nil {
err1 := removeFile(resolvPath)
Copy link
Member

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.

resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config)
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath)
if err != nil {
err1 := removeFile(resolvPath)
Copy link
Member

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.

Copy link
Member

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.

resolvPath := getResolvPath(getSandboxRootDir(c.rootDir, id), config)
err := parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options, resolvPath)
if err != nil {
err1 := removeFile(resolvPath)
Copy link
Member

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.

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)
Copy link
Member

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.

@@ -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 {
Copy link
Member

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.

Copy link
Contributor Author

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...

@@ -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.
Copy link
Member

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.

Copy link
Member

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.

// 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"})
Copy link
Member

@Random-Liu Random-Liu Jun 1, 2017

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.

@@ -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)
Copy link
Member

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.

@Random-Liu
Copy link
Member

@Crazykev Please rebase your PR and address comments

@Crazykev
Copy link
Contributor Author

Crazykev commented Jun 6, 2017

@Random-Liu Addressed comments and added related unit test. PTAL.

resolvPath := getResolvPath(sandboxRootDir)
if len(resolvContent) == 0 && hostNetwork {
// copy host's resolv.conf to resolvPath
// err = c.os.CopyFile(resolvConfPath, resolvPath)
Copy link
Contributor Author

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.

@Random-Liu Random-Liu mentioned this pull request Jun 7, 2017
@Crazykev Crazykev force-pushed the resolv branch 2 times, most recently from 7c6bccb to 5668536 Compare June 7, 2017 04:44
@Crazykev
Copy link
Contributor Author

Crazykev commented Jun 7, 2017

@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,
Copy link
Member

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.

@@ -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.
Copy link
Member

Choose a reason for hiding this comment

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

s/WriteFile/WrteFileFn.


// 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.
Copy link
Member

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.

// 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
Copy link
Member

Choose a reason for hiding this comment

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

s/TODO/TODO:

@@ -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 {
Copy link
Member

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.

// copy host's resolv.conf to resolvPath
err = c.os.CopyFile(resolvConfPath, resolvPath, 0755)
} else {
err = c.os.WriteFile(resolvPath, resolvContent, 0755)
Copy link
Member

Choose a reason for hiding this comment

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

Same, should be 0644.

err = c.os.WriteFile(resolvPath, resolvContent, 0755)
}
if err != nil {
return fmt.Errorf("failed to copy/write file to %q: %v", resolvPath, err)
Copy link
Member

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.

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)
Copy link
Member

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)

servers: []string{"8.8.8.8", "server.google.com"},
searches: []string{"114.114.114.114"},
options: []string{"timeout:1"},
},
Copy link
Member

Choose a reason for hiding this comment

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

  1. Let's add test case to check whether the function returns error when searches is more than 6
  2. Let's add expectedOutput into the test case, and directly check assert.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": {
   /*...*/
  },
}


// 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) {
Copy link
Member

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.

@Random-Liu
Copy link
Member

Random-Liu commented Jun 7, 2017

@Crazykev Reviewed. Nits are nice to address. Others please take a look.

@Crazykev Crazykev force-pushed the resolv branch 2 times, most recently from e1f793c to 0ab02d0 Compare June 8, 2017 03:28
@Crazykev
Copy link
Contributor Author

Crazykev commented Jun 8, 2017

@Random-Liu Updated, PTAL

Copy link
Member

@Random-Liu Random-Liu left a 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.

// TODO: Need to figure out whether we should always mount it as read-only
mounts = append(mounts, &runtime.Mount{
ContainerPath: resolvConfPath,
HostPath: resolvPath,
Copy link
Member

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.

},
{
ContainerPath: resolvConfPath,
HostPath: getResolvPath(testSandboxRootDir),
Copy link
Member

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.

}
resolvPath := getResolvPath(rootDir)
hostNetwork := config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork()
if resolvContent == "" && hostNetwork {
Copy link
Member

@Random-Liu Random-Liu Jun 8, 2017

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
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Random-Liu

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.

Copy link
Member

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.

}
resolvPath := getResolvPath(rootDir)
hostNetwork := config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostNetwork()
if resolvContent == "" && hostNetwork {
Copy link
Member

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.

nSearches := len(searches)
nOptions := len(options)
resolvContent := ""
if nServers == 0 && nSearches == 0 && nOptions == 0 {
Copy link
Member

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.

@Crazykev
Copy link
Contributor Author

Crazykev commented Jun 9, 2017

@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>
Copy link
Member

@Random-Liu Random-Liu left a 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),
Copy link
Member

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.

@Random-Liu Random-Liu added the lgtm label Jun 9, 2017
@Random-Liu Random-Liu merged commit 227dbe9 into containerd:master Jun 9, 2017
@Crazykev Crazykev deleted the resolv branch June 10, 2017 11:45
lanchongyizu pushed a commit to lanchongyizu/cri-containerd that referenced this pull request Sep 3, 2017
Generate and maintain resolv.conf for sandbox
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants