Skip to content

Commit 0b774e5

Browse files
authored
Merge pull request containerd#60 from Random-Liu/always-add-etc-hosts
Add sandbox /etc/hosts.
2 parents 71734f3 + d878de2 commit 0b774e5

File tree

7 files changed

+187
-23
lines changed

7 files changed

+187
-23
lines changed

pkg/os/os.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type OS interface {
3232
RemoveAll(path string) error
3333
OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
3434
Stat(name string) (os.FileInfo, error)
35+
CopyFile(src, dest string, perm os.FileMode) error
3536
}
3637

3738
// RealOS is used to dispatch the real system level operations.
@@ -56,3 +57,21 @@ func (RealOS) OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMod
5657
func (RealOS) Stat(name string) (os.FileInfo, error) {
5758
return os.Stat(name)
5859
}
60+
61+
// CopyFile copys src file to dest file
62+
func (RealOS) CopyFile(src, dest string, perm os.FileMode) error {
63+
in, err := os.Open(src)
64+
if err != nil {
65+
return err
66+
}
67+
defer in.Close()
68+
69+
out, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
70+
if err != nil {
71+
return err
72+
}
73+
defer out.Close()
74+
75+
_, err = io.Copy(out, in)
76+
return err
77+
}

pkg/os/testing/fake_os.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ type FakeOS struct {
3434
MkdirAllFn func(string, os.FileMode) error
3535
RemoveAllFn func(string) error
3636
OpenFifoFn func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error)
37-
StatFn func(name string) (os.FileInfo, error)
37+
StatFn func(string) (os.FileInfo, error)
38+
CopyFileFn func(string, string, os.FileMode) error
3839
errors map[string]error
3940
}
4041

@@ -118,7 +119,7 @@ func (f *FakeOS) OpenFifo(ctx context.Context, fn string, flag int, perm os.File
118119
return nil, nil
119120
}
120121

121-
// Stat is a fake call that invokes Stat or just return nil.
122+
// Stat is a fake call that invokes StatFn or just return nil.
122123
func (f *FakeOS) Stat(name string) (os.FileInfo, error) {
123124
if err := f.getError("Stat"); err != nil {
124125
return nil, err
@@ -129,3 +130,15 @@ func (f *FakeOS) Stat(name string) (os.FileInfo, error) {
129130
}
130131
return nil, nil
131132
}
133+
134+
// CopyFile is a fake call that invokes CopyFileFn or just return nil.
135+
func (f *FakeOS) CopyFile(src, dest string, perm os.FileMode) error {
136+
if err := f.getError("CopyFile"); err != nil {
137+
return err
138+
}
139+
140+
if f.CopyFileFn != nil {
141+
return f.CopyFileFn(src, dest, perm)
142+
}
143+
return nil
144+
}

pkg/server/container_start.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"io"
2323
"os"
2424
"path/filepath"
25+
"strings"
2526
"time"
2627

2728
"github.com/containerd/containerd/api/services/execution"
@@ -119,7 +120,10 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me
119120
if err != nil {
120121
return fmt.Errorf("failed to get container image %q: %v", meta.ImageRef, err)
121122
}
122-
spec, err := c.generateContainerSpec(id, sandboxPid, config, sandboxConfig, imageMeta.Config)
123+
124+
mounts := c.generateContainerMounts(getSandboxRootDir(c.rootDir, sandboxID), config)
125+
126+
spec, err := c.generateContainerSpec(id, sandboxPid, config, sandboxConfig, imageMeta.Config, mounts)
123127
if err != nil {
124128
return fmt.Errorf("failed to generate container %q spec: %v", id, err)
125129
}
@@ -218,7 +222,7 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me
218222
}
219223

220224
func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig,
221-
sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig) (*runtimespec.Spec, error) {
225+
sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig, extraMounts []*runtime.Mount) (*runtimespec.Spec, error) {
222226
// Creates a spec Generator with the default spec.
223227
// TODO(random-liu): [P2] Move container runtime spec generation into a helper function.
224228
g := generate.New()
@@ -246,7 +250,8 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
246250
g.AddProcessEnv(e.GetKey(), e.GetValue())
247251
}
248252

249-
addOCIBindMounts(&g, config.GetMounts())
253+
// Add extra mounts first so that CRI specified mounts can override.
254+
addOCIBindMounts(&g, append(extraMounts, config.GetMounts()...))
250255

251256
// TODO(random-liu): [P1] Set device mapping.
252257
// Ref https://github.com/moby/moby/blob/master/oci/devices_linux.go.
@@ -294,6 +299,21 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
294299
return g.Spec(), nil
295300
}
296301

302+
// generateContainerMounts sets up necessary container mounts including /dev/shm, /etc/hosts
303+
// and /etc/resolv.conf.
304+
func (c *criContainerdService) generateContainerMounts(sandboxRootDir string, config *runtime.ContainerConfig) []*runtime.Mount {
305+
var mounts []*runtime.Mount
306+
securityContext := config.GetLinux().GetSecurityContext()
307+
mounts = append(mounts, &runtime.Mount{
308+
ContainerPath: etcHosts,
309+
HostPath: getSandboxHosts(sandboxRootDir),
310+
Readonly: securityContext.ReadonlyRootfs,
311+
})
312+
// TODO(random-liu): [P0] Mount sandbox resolv.config.
313+
// TODO(random-liu): [P0] Mount sandbox /dev/shm.
314+
return mounts
315+
}
316+
297317
// setOCIProcessArgs sets process args. It returns error if the final arg list
298318
// is empty.
299319
func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) error {
@@ -315,6 +335,19 @@ func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, i
315335
return nil
316336
}
317337

338+
// addImageEnvs adds environment variables from image config. It returns error if
339+
// an invalid environment variable is encountered.
340+
func addImageEnvs(g *generate.Generator, imageEnvs []string) error {
341+
for _, e := range imageEnvs {
342+
kv := strings.Split(e, "=")
343+
if len(kv) != 2 {
344+
return fmt.Errorf("invalid environment variable %q", e)
345+
}
346+
g.AddProcessEnv(kv[0], kv[1])
347+
}
348+
return nil
349+
}
350+
318351
// addOCIBindMounts adds bind mounts.
319352
func addOCIBindMounts(g *generate.Generator, mounts []*runtime.Mount) {
320353
for _, mount := range mounts {

pkg/server/container_start_test.go

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func TestGeneralContainerSpec(t *testing.T) {
176176
testPid := uint32(1234)
177177
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
178178
c := newTestCRIContainerdService()
179-
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig)
179+
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil)
180180
assert.NoError(t, err)
181181
specCheck(t, testID, testPid, spec)
182182
}
@@ -188,7 +188,7 @@ func TestContainerSpecTty(t *testing.T) {
188188
c := newTestCRIContainerdService()
189189
for _, tty := range []bool{true, false} {
190190
config.Tty = tty
191-
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig)
191+
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil)
192192
assert.NoError(t, err)
193193
specCheck(t, testID, testPid, spec)
194194
assert.Equal(t, tty, spec.Process.Terminal)
@@ -202,13 +202,46 @@ func TestContainerSpecReadonlyRootfs(t *testing.T) {
202202
c := newTestCRIContainerdService()
203203
for _, readonly := range []bool{true, false} {
204204
config.Linux.SecurityContext.ReadonlyRootfs = readonly
205-
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig)
205+
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil)
206206
assert.NoError(t, err)
207207
specCheck(t, testID, testPid, spec)
208208
assert.Equal(t, readonly, spec.Root.Readonly)
209209
}
210210
}
211211

212+
func TestContainerSpecWithExtraMounts(t *testing.T) {
213+
testID := "test-id"
214+
testPid := uint32(1234)
215+
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
216+
c := newTestCRIContainerdService()
217+
mountInConfig := &runtime.Mount{
218+
ContainerPath: "test-container-path",
219+
HostPath: "test-host-path",
220+
Readonly: false,
221+
}
222+
config.Mounts = append(config.Mounts, mountInConfig)
223+
extraMount := &runtime.Mount{
224+
ContainerPath: "test-container-path",
225+
HostPath: "test-host-path-extra",
226+
Readonly: true,
227+
}
228+
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, []*runtime.Mount{extraMount})
229+
assert.NoError(t, err)
230+
specCheck(t, testID, testPid, spec)
231+
var mounts []runtimespec.Mount
232+
for _, m := range spec.Mounts {
233+
if m.Destination == "test-container-path" {
234+
mounts = append(mounts, m)
235+
}
236+
}
237+
t.Logf("Extra mounts should come first")
238+
require.Len(t, mounts, 2)
239+
assert.Equal(t, "test-host-path-extra", mounts[0].Source)
240+
assert.Contains(t, mounts[0].Options, "ro")
241+
assert.Equal(t, "test-host-path", mounts[1].Source)
242+
assert.Contains(t, mounts[1].Options, "rw")
243+
}
244+
212245
func TestContainerSpecCommand(t *testing.T) {
213246
for desc, test := range map[string]struct {
214247
criEntrypoint []string
@@ -270,6 +303,46 @@ func TestContainerSpecCommand(t *testing.T) {
270303
}
271304
}
272305

306+
func TestGenerateContainerMounts(t *testing.T) {
307+
testSandboxRootDir := "test-sandbox-root"
308+
for desc, test := range map[string]struct {
309+
securityContext *runtime.LinuxContainerSecurityContext
310+
expectedMounts []*runtime.Mount
311+
}{
312+
"should setup ro /etc/hosts mount when rootfs is read-only": {
313+
securityContext: &runtime.LinuxContainerSecurityContext{
314+
ReadonlyRootfs: true,
315+
},
316+
expectedMounts: []*runtime.Mount{{
317+
ContainerPath: "/etc/hosts",
318+
HostPath: testSandboxRootDir + "/hosts",
319+
Readonly: true,
320+
}},
321+
},
322+
"should setup rw /etc/hosts mount when rootfs is read-write": {
323+
securityContext: &runtime.LinuxContainerSecurityContext{},
324+
expectedMounts: []*runtime.Mount{{
325+
ContainerPath: "/etc/hosts",
326+
HostPath: testSandboxRootDir + "/hosts",
327+
Readonly: false,
328+
}},
329+
},
330+
} {
331+
config := &runtime.ContainerConfig{
332+
Metadata: &runtime.ContainerMetadata{
333+
Name: "test-name",
334+
Attempt: 1,
335+
},
336+
Linux: &runtime.LinuxContainerConfig{
337+
SecurityContext: test.securityContext,
338+
},
339+
}
340+
c := newTestCRIContainerdService()
341+
mounts := c.generateContainerMounts(testSandboxRootDir, config)
342+
assert.Equal(t, test.expectedMounts, mounts, desc)
343+
}
344+
}
345+
273346
func TestStartContainer(t *testing.T) {
274347
testID := "test-id"
275348
testSandboxID := "test-sandbox-id"

pkg/server/helpers.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const (
8585
utsNSFormat = "/proc/%v/ns/uts"
8686
// pidNSFormat is the format of pid namespace of a process.
8787
pidNSFormat = "/proc/%v/ns/pid"
88+
// etcHosts is the default path of /etc/hosts file.
89+
etcHosts = "/etc/hosts"
8890
)
8991

9092
// generateID generates a random unique id.
@@ -133,14 +135,20 @@ func getContainerRootDir(rootDir, id string) string {
133135
return filepath.Join(rootDir, containersDir, id)
134136
}
135137

136-
// getStreamingPipes returns the stdin/stdout/stderr pipes path in the root.
138+
// getStreamingPipes returns the stdin/stdout/stderr pipes path in the
139+
// container/sandbox root.
137140
func getStreamingPipes(rootDir string) (string, string, string) {
138141
stdin := filepath.Join(rootDir, stdinNamedPipe)
139142
stdout := filepath.Join(rootDir, stdoutNamedPipe)
140143
stderr := filepath.Join(rootDir, stderrNamedPipe)
141144
return stdin, stdout, stderr
142145
}
143146

147+
// getSandboxHosts returns the hosts file path inside the sandbox root directory.
148+
func getSandboxHosts(sandboxRootDir string) string {
149+
return filepath.Join(sandboxRootDir, "hosts")
150+
}
151+
144152
// prepareStreamingPipes prepares stream named pipe for container. returns nil
145153
// streaming handler if corresponding stream path is empty.
146154
func (c *criContainerdService) prepareStreamingPipes(ctx context.Context, stdin, stdout, stderr string) (

pkg/server/sandbox_run.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package server
1919
import (
2020
"encoding/json"
2121
"fmt"
22-
"strings"
2322
"time"
2423

2524
"github.com/containerd/containerd/api/services/execution"
@@ -133,6 +132,13 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
133132
return nil, fmt.Errorf("failed to start sandbox stderr logger: %v", err)
134133
}
135134

135+
// Setup sandbox /dev/shm, /etc/hosts and /etc/resolv.conf.
136+
if err = c.setupSandboxFiles(sandboxRootDir, config); err != nil {
137+
return nil, fmt.Errorf("failed to setup sandbox files: %v", err)
138+
}
139+
// No need to cleanup on error, because the whole sandbox root directory will be removed
140+
// on error.
141+
136142
// Start sandbox container.
137143
spec, err := c.generateSandboxContainerSpec(id, config, imageMeta.Config)
138144
if err != nil {
@@ -239,8 +245,6 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
239245
// Set hostname.
240246
g.SetHostname(config.GetHostname())
241247

242-
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox.
243-
244248
// TODO(random-liu): [P0] Add NamespaceGetter and PortMappingGetter to initialize network plugin.
245249

246250
// TODO(random-liu): [P0] Add annotation to identify the container is managed by cri-containerd.
@@ -270,8 +274,6 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
270274
g.RemoveLinuxNamespace(string(runtimespec.PIDNamespace)) // nolint: errcheck
271275
}
272276

273-
// TODO(random-liu): [P0] Deal with /dev/shm. Use host for HostIpc, and create and mount for
274-
// non-HostIpc. What about mqueue?
275277
if nsOptions.GetHostIpc() {
276278
g.RemoveLinuxNamespace(string(runtimespec.IPCNamespace)) // nolint: errcheck
277279
}
@@ -293,15 +295,16 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
293295
return g.Spec(), nil
294296
}
295297

296-
// addImageEnvs adds environment variables from image config. It returns error if
297-
// an invalid environment variable is encountered.
298-
func addImageEnvs(g *generate.Generator, imageEnvs []string) error {
299-
for _, e := range imageEnvs {
300-
kv := strings.Split(e, "=")
301-
if len(kv) != 2 {
302-
return fmt.Errorf("invalid environment variable %q", e)
303-
}
304-
g.AddProcessEnv(kv[0], kv[1])
298+
// setupSandboxFiles sets up necessary sandbox files including /dev/shm, /etc/hosts
299+
// and /etc/resolv.conf.
300+
func (c *criContainerdService) setupSandboxFiles(rootDir string, config *runtime.PodSandboxConfig) error {
301+
// TODO(random-liu): Consider whether we should maintain /etc/hosts and /etc/resolv.conf in kubelet.
302+
sandboxEtcHosts := getSandboxHosts(rootDir)
303+
if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0666); err != nil {
304+
return fmt.Errorf("failed to generate sandbox hosts file %q: %v", sandboxEtcHosts, err)
305305
}
306+
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox.
307+
// TODO(random-liu): [P0] Deal with /dev/shm. Use host for HostIpc, and create and mount for
308+
// non-HostIpc. What about mqueue?
306309
return nil
307310
}

pkg/server/sandbox_run_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,21 @@ func TestGenerateSandboxContainerSpec(t *testing.T) {
157157
}
158158
}
159159

160+
func TestSetupSandboxFiles(t *testing.T) {
161+
testRootDir := "test-sandbox-root"
162+
expectedCopys := [][]interface{}{
163+
{"/etc/hosts", testRootDir + "/hosts", os.FileMode(0666)},
164+
}
165+
c := newTestCRIContainerdService()
166+
var copys [][]interface{}
167+
c.os.(*ostesting.FakeOS).CopyFileFn = func(src string, dest string, perm os.FileMode) error {
168+
copys = append(copys, []interface{}{src, dest, perm})
169+
return nil
170+
}
171+
c.setupSandboxFiles(testRootDir, nil)
172+
assert.Equal(t, expectedCopys, copys, "should copy /etc/hosts for sandbox")
173+
}
174+
160175
func TestRunPodSandbox(t *testing.T) {
161176
config, imageConfig, specCheck := getRunPodSandboxTestData()
162177
c := newTestCRIContainerdService()

0 commit comments

Comments
 (0)