Skip to content

Commit ae58ddf

Browse files
markzhang0928aylei
authored andcommitted
enhancement: support target-container procfs correct (#83)
* enhancement: support target-container procfs correct * update Makefile remove some wrong adjustments for my own dev environment * Update cmd.go fix my typo error I made for travis CI * fix: agent_daemonset yaml support lxcfs * add:使用alpine linux,减小debug-agent镜像大小 * fix: 修复nsenter在alpine发行版中的一些error * fix: reinforce the compatibility of debug-agent for kubectl-debug && fix typo && go fmt
1 parent 63e1ecc commit ae58ddf

File tree

11 files changed

+308
-37
lines changed

11 files changed

+308
-37
lines changed

.dockerignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
*
22
!debug-agent
3-
3+
!./scripts/start.sh

Dockerfile

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
FROM gcr.io/distroless/static
1+
FROM ubuntu:xenial as build
22

3+
RUN apt-get update && apt-get install libcgmanager-dev libnih-dbus-dev libnih-dev libfuse-dev automake libtool libpam-dev wget gcc automake -y && apt-get clean && rm -rf /var/lib/apt/lists/*
4+
5+
ENV LXCFS_VERSION 3.1.2
6+
RUN wget https://linuxcontainers.org/downloads/lxcfs/lxcfs-$LXCFS_VERSION.tar.gz && \
7+
mkdir /lxcfs && tar xzvf lxcfs-$LXCFS_VERSION.tar.gz -C /lxcfs --strip-components=1 && \
8+
cd /lxcfs && ./configure && make
9+
10+
FROM alpine:3.10
11+
12+
COPY --from=build /lxcfs/lxcfs /usr/local/bin/lxcfs
13+
COPY --from=build /lxcfs/.libs/liblxcfs.so /usr/local/lib/lxcfs/liblxcfs.so
14+
COPY --from=build /lxcfs/lxcfs /lxcfs/lxcfs
15+
COPY --from=build /lxcfs/.libs/liblxcfs.so /lxcfs/liblxcfs.so
16+
COPY ./scripts/start.sh /
317
COPY ./debug-agent /bin/debug-agent
18+
419
EXPOSE 10027
520

6-
ENTRYPOINT ["/bin/debug-agent"]
21+
CMD ["/start.sh"]

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugin:
1212
GO111MODULE=on CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o kubectl-debug cmd/plugin/main.go
1313

1414
agent-docker: agent
15-
docker build . -t aylei/debug-agent:latest
15+
docker build . -t aylei/debug-agent:0.0.2
1616

1717
agent:
1818
$(GO) build -ldflags '$(LDFLAGS)' -o debug-agent cmd/agent/main.go

pkg/agent/lxcfs.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package agent
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"os"
9+
)
10+
11+
// List of LXC filesystem files
12+
const (
13+
MemFile string = "/proc/meminfo"
14+
CpuFile string = "/proc/cpuinfo"
15+
UpTimeFile string = "/proc/uptime"
16+
SwapsFile string = "/proc/swaps"
17+
StatFile string = "/proc/stat"
18+
DiskStatsFile string = "/proc/diskstats"
19+
LoadavgFile string = "/proc/loadavg"
20+
)
21+
22+
var (
23+
// IsLxcfsEnabled means whether to enable lxcfs
24+
LxcfsEnabled bool
25+
26+
// LxcfsRootDir
27+
LxcfsRootDir = "/var/lib/lxc"
28+
29+
// LxcfsHomeDir means /var/lib/lxc/lxcfs
30+
LxcfsHomeDir = "/var/lib/lxc/lxcfs"
31+
32+
// LxcfsFiles is a list of LXC files
33+
LxcfsProcFiles = []string{MemFile, CpuFile, UpTimeFile, SwapsFile, StatFile, DiskStatsFile, LoadavgFile}
34+
)
35+
36+
// CheckLxcfsMount check if the the mount point of lxcfs exists
37+
func CheckLxcfsMount() error {
38+
isMount := false
39+
f, err := os.Open("/proc/1/mountinfo")
40+
if err != nil {
41+
return fmt.Errorf("Check lxcfs mounts failed: %v", err)
42+
}
43+
fr := bufio.NewReader(f)
44+
for {
45+
line, err := fr.ReadBytes('\n')
46+
if err != nil {
47+
if err == io.EOF {
48+
break
49+
}
50+
return fmt.Errorf("Check lxcfs mounts failed: %v", err)
51+
}
52+
53+
if bytes.Contains(line, []byte(LxcfsHomeDir)) {
54+
isMount = true
55+
break
56+
}
57+
}
58+
if !isMount {
59+
return fmt.Errorf("%s is not a mount point, please run \" lxcfs %s \" before debug", LxcfsHomeDir, LxcfsHomeDir)
60+
}
61+
return nil
62+
}

pkg/agent/runtime.go

+48-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99
"log"
1010
"time"
1111

12-
term "github.com/aylei/kubectl-debug/pkg/util"
12+
"github.com/aylei/kubectl-debug/pkg/nsenter"
13+
"github.com/aylei/kubectl-debug/pkg/util"
1314
"github.com/docker/docker/api/types"
1415
"github.com/docker/docker/api/types/container"
1516
"github.com/docker/docker/api/types/strslice"
@@ -41,11 +42,12 @@ func NewRuntimeManager(host string, timeout time.Duration) (*RuntimeManager, err
4142
// DebugAttacher implements Attacher
4243
// we use this struct in order to inject debug info (image, command) in the debug procedure
4344
type DebugAttacher struct {
44-
runtime *RuntimeManager
45-
image string
46-
authStr string
47-
command []string
48-
client *dockerclient.Client
45+
runtime *RuntimeManager
46+
image string
47+
authStr string
48+
lxcfsEnabled bool
49+
command []string
50+
client *dockerclient.Client
4951

5052
// control the preparing of debug container
5153
stopListenEOF chan struct{}
@@ -58,11 +60,12 @@ func (a *DebugAttacher) AttachContainer(name string, uid kubetype.UID, container
5860
}
5961

6062
// GetAttacher returns an implementation of Attacher
61-
func (m *RuntimeManager) GetAttacher(image string, authStr string, command []string, context context.Context, cancel context.CancelFunc) kubeletremote.Attacher {
63+
func (m *RuntimeManager) GetAttacher(image, authStr string, lxcfsEnabled bool, command []string, context context.Context, cancel context.CancelFunc) kubeletremote.Attacher {
6264
return &DebugAttacher{
6365
runtime: m,
6466
image: image,
6567
authStr: authStr,
68+
lxcfsEnabled: lxcfsEnabled,
6669
command: command,
6770
context: context,
6871
client: m.client,
@@ -101,6 +104,17 @@ func (m *DebugAttacher) DebugContainer(container, image string, authStr string,
101104
// }
102105
// }
103106
//} ()
107+
// step 0: set container procfs correct by lxcfs
108+
stdout.Write([]byte(fmt.Sprintf("set container procfs correct %t .. \n\r", m.lxcfsEnabled)))
109+
if m.lxcfsEnabled {
110+
if err := CheckLxcfsMount(); err != nil {
111+
return err
112+
}
113+
114+
if err := m.SetContainerLxcfs(container); err != nil {
115+
return err
116+
}
117+
}
104118

105119
// step 1: pull image
106120
stdout.Write([]byte(fmt.Sprintf("pulling image %s... \n\r", image)))
@@ -129,6 +143,33 @@ func (m *DebugAttacher) DebugContainer(container, image string, authStr string,
129143
return nil
130144
}
131145

146+
func (m *DebugAttacher) SetContainerLxcfs(container string) error {
147+
ctx, cancel := m.getContextWithTimeout()
148+
defer cancel()
149+
containerInstance, err := m.client.ContainerInspect(ctx, container)
150+
if err != nil {
151+
return err
152+
}
153+
for _, mount := range containerInstance.Mounts {
154+
if mount.Destination == LxcfsRootDir {
155+
log.Printf("remount lxcfs when the rootdir of lxcfs of target container has been mounted. \n\t ")
156+
for _, procfile := range LxcfsProcFiles {
157+
nsenter := &nsenter.MountNSEnter{
158+
Target: containerInstance.State.Pid,
159+
MountLxcfs: true,
160+
}
161+
_, stderr, err := nsenter.Execute("--", "mount", "-B", LxcfsHomeDir+procfile, procfile)
162+
if err != nil {
163+
log.Printf("bind mount lxcfs files failed. \n\t reason: %s", stderr)
164+
return err
165+
}
166+
}
167+
}
168+
}
169+
170+
return nil
171+
}
172+
132173
// Run a new container, this container will join the network,
133174
// mount, and pid namespace of the given container
134175
func (m *DebugAttacher) RunDebugContainer(targetId string, image string, command []string) (string, error) {

pkg/agent/server.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ func (s *Server) ServeDebug(w http.ResponseWriter, req *http.Request) {
9797
Stderr: false,
9898
TTY: true,
9999
}
100+
lxcfsEnabled := req.FormValue("lxcfsEnabled")
101+
if lxcfsEnabled == "" || lxcfsEnabled == "false" {
102+
LxcfsEnabled = false
103+
} else if lxcfsEnabled == "true" {
104+
LxcfsEnabled = true
105+
}
100106

101107
context, cancel := context.WithCancel(req.Context())
102108
defer cancel()
@@ -105,7 +111,7 @@ func (s *Server) ServeDebug(w http.ResponseWriter, req *http.Request) {
105111
kubeletremote.ServeAttach(
106112
w,
107113
req,
108-
s.runtimeApi.GetAttacher(image, authStr, commandSlice, context, cancel),
114+
s.runtimeApi.GetAttacher(image, authStr, LxcfsEnabled, commandSlice, context, cancel),
109115
"",
110116
"",
111117
dockerContainerId,

pkg/nsenter/nsenter.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package nsenter
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os/exec"
8+
"strconv"
9+
)
10+
11+
// MountNSEnter is the client used to enter the mount namespace
12+
type MountNSEnter struct {
13+
Target int // target PID (required)
14+
MountLxcfs bool // enter mount namespace or not
15+
MountFile string // Mount namespace location, default to /proc/PID/ns/mnt
16+
}
17+
18+
// Execute runs the given command with a default background context
19+
func (cli *MountNSEnter) Execute(command string, args ...string) (stdout, stderr string, err error) {
20+
return cli.ExecuteContext(context.Background(), command, args...)
21+
}
22+
23+
// ExecuteContext the given command using the specific nsenter config
24+
func (cli *MountNSEnter) ExecuteContext(ctx context.Context, command string, args ...string) (string, string, error) {
25+
cmd, err := cli.setCommand(ctx)
26+
if err != nil {
27+
return "", "", fmt.Errorf("Error when set command: %v", err)
28+
}
29+
30+
var stdout, stderr bytes.Buffer
31+
cmd.Stdout = &stdout
32+
cmd.Stderr = &stderr
33+
cmd.Args = append(cmd.Args, command)
34+
cmd.Args = append(cmd.Args, args...)
35+
36+
err = cmd.Run()
37+
if err != nil {
38+
return stdout.String(), stderr.String(), fmt.Errorf("Error while executing command: %v", err)
39+
}
40+
41+
return stdout.String(), stderr.String(), nil
42+
}
43+
44+
func (cli *MountNSEnter) setCommand(ctx context.Context) (*exec.Cmd, error) {
45+
if cli.Target == 0 {
46+
return nil, fmt.Errorf("Target must be specified")
47+
}
48+
var args []string
49+
args = append(args, "--target", strconv.Itoa(cli.Target))
50+
51+
if cli.MountLxcfs {
52+
if cli.MountFile != "" {
53+
args = append(args, fmt.Sprintf("--mount=%s", cli.MountFile))
54+
} else {
55+
args = append(args, "--mount")
56+
}
57+
}
58+
59+
cmd := exec.CommandContext(ctx, "/usr/bin/nsenter", args...)
60+
return cmd, nil
61+
}

pkg/plugin/cmd.go

+49
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ You may set default configuration such as image and command in the config file,
8585

8686
defaultPortForward = true
8787
defaultAgentless = true
88+
defaultLxcfsEnable = true
8889
)
8990

9091
// DebugOptions specify how to run debug container in a running pod
@@ -114,6 +115,8 @@ type DebugOptions struct {
114115
AgentPodNamespace string
115116
AgentPodNode string
116117
AgentPodResource agentPodResources
118+
// enable lxcfs
119+
IsLxcfsEnabled bool
117120

118121
Flags *genericclioptions.ConfigFlags
119122
CoreClient coreclient.CoreV1Interface
@@ -214,6 +217,8 @@ func NewDebugCmd(streams genericclioptions.IOStreams) *cobra.Command {
214217
fmt.Sprintf("Agentless mode, agent pod cpu limits, default is not set"))
215218
cmd.Flags().StringVar(&opts.AgentPodResource.MemoryLimits, "agent-pod-memory-limits", "",
216219
fmt.Sprintf("Agentless mode, agent pod memory limits, default is not set"))
220+
cmd.Flags().BoolVarP(&opts.IsLxcfsEnabled, "enable-lxcfs", "", true,
221+
fmt.Sprintf("Enable Lxcfs, the target container can use its proc files, default to %t", defaultLxcfsEnable))
217222
opts.Flags.AddFlags(cmd.Flags())
218223

219224
return cmd
@@ -372,6 +377,12 @@ func (o *DebugOptions) Complete(cmd *cobra.Command, args []string, argsLenAtDash
372377
}
373378
}
374379

380+
if o.IsLxcfsEnabled {
381+
o.IsLxcfsEnabled = config.IsLxcfsEnabled
382+
} else {
383+
o.IsLxcfsEnabled = defaultLxcfsEnable
384+
}
385+
375386
if config.PortForward {
376387
o.PortForward = true
377388
}
@@ -534,6 +545,11 @@ func (o *DebugOptions) Run() error {
534545
params := url.Values{}
535546
params.Add("image", o.Image)
536547
params.Add("container", containerID)
548+
if o.IsLxcfsEnabled {
549+
params.Add("lxcfsEnabled", "true")
550+
} else {
551+
params.Add("lxcfsEnabled", "false")
552+
}
537553
var authStr string
538554
registrySecret, err := o.CoreClient.Secrets(o.RegistrySecretNamespace).Get(o.RegistrySecretName, v1.GetOptions{})
539555
if err != nil {
@@ -725,6 +741,9 @@ func (o *DebugOptions) launchPod(pod *corev1.Pod) (*corev1.Pod, error) {
725741

726742
// getAgentPod construnct agentPod from agent pod template
727743
func (o *DebugOptions) getAgentPod() *corev1.Pod {
744+
prop := corev1.MountPropagationBidirectional
745+
directoryCreate := corev1.HostPathDirectoryOrCreate
746+
priveleged := true
728747
agentPod := &corev1.Pod{
729748
TypeMeta: v1.TypeMeta{
730749
Kind: "Pod",
@@ -735,6 +754,7 @@ func (o *DebugOptions) getAgentPod() *corev1.Pod {
735754
Namespace: o.AgentPodNamespace,
736755
},
737756
Spec: corev1.PodSpec{
757+
HostPID: true,
738758
NodeName: o.AgentPodNode,
739759
Containers: []corev1.Container{
740760
{
@@ -754,12 +774,24 @@ func (o *DebugOptions) getAgentPod() *corev1.Pod {
754774
TimeoutSeconds: 1,
755775
FailureThreshold: 3,
756776
},
777+
SecurityContext: &corev1.SecurityContext{
778+
Privileged: &priveleged,
779+
},
757780
Resources: o.buildAgentResourceRequirements(),
758781
VolumeMounts: []corev1.VolumeMount{
759782
{
760783
Name: "docker",
761784
MountPath: "/var/run/docker.sock",
762785
},
786+
{
787+
Name: "cgroup",
788+
MountPath: "/sys/fs/cgroup",
789+
},
790+
{
791+
Name: "lxcfs",
792+
MountPath: "/var/lib/lxc/lxcfs",
793+
MountPropagation: &prop,
794+
},
763795
},
764796
Ports: []corev1.ContainerPort{
765797
{
@@ -779,6 +811,23 @@ func (o *DebugOptions) getAgentPod() *corev1.Pod {
779811
},
780812
},
781813
},
814+
{
815+
Name: "cgroup",
816+
VolumeSource: corev1.VolumeSource{
817+
HostPath: &corev1.HostPathVolumeSource{
818+
Path: "/sys/fs/cgroup",
819+
},
820+
},
821+
},
822+
{
823+
Name: "lxcfs",
824+
VolumeSource: corev1.VolumeSource{
825+
HostPath: &corev1.HostPathVolumeSource{
826+
Path: "/var/lib/lxc/lxcfs",
827+
Type: &directoryCreate,
828+
},
829+
},
830+
},
782831
},
783832
RestartPolicy: corev1.RestartPolicyNever,
784833
},

0 commit comments

Comments
 (0)