├── .dockerignore
├── .gitignore
├── .goreleaser.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd
├── debug-agent
│ └── main.go
└── kubectl-debug
│ └── main.go
├── go.mod
├── go.sum
├── pkg
├── debug-agent
│ ├── config.go
│ ├── lxcfs.go
│ ├── resize.go
│ ├── runtime.go
│ └── server.go
├── kubectl-debug
│ ├── cmd.go
│ └── config.go
├── nsenter
│ └── nsenter.go
└── util
│ ├── jsonstream.go
│ ├── resize.go
│ ├── resizeevents.go
│ ├── resizeevents_windows.go
│ └── term.go
├── scripts
├── docker_push.sh
└── start.sh
├── version.sh
└── version
└── version.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !debug-agent
3 | !./scripts/start.sh
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .uptodate
2 | .pkg
3 | .cache
4 | *.output
5 | .swp
6 | vendor/
7 | /debug-agent
8 | /kubectl-debug
9 | dist/
10 | .vscode/
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | project_name: kubectl-debug
2 | before:
3 | hooks:
4 | # you may remove this if you don't use vgo
5 | - go mod download
6 | builds:
7 | - env:
8 | - CGO_ENABLED=0
9 | - GO111MODULE=on
10 | binary: kubectl-debug
11 | main: ./cmd/kubectl-debug/main.go
12 | goos:
13 | - freebsd
14 | - windows
15 | - linux
16 | - darwin
17 | goarch:
18 | - amd64
19 | - 386
20 | ignore:
21 | - goos: darwin
22 | goarch: 386
23 | ldflags:
24 | - -s -w -X 'github.com/jamestgrant/kubectl-debug/version.gitVersion={{.Version}}'
25 | checksum:
26 | name_template: 'checksums.txt'
27 | snapshot:
28 | name_template: "{{ .Tag }}-next"
29 | changelog:
30 | sort: asc
31 | filters:
32 | exclude:
33 | - '^docs:'
34 | - '^test:'
35 | brew:
36 | github:
37 | owner: jamestgrant
38 | name: homebrew-tap
39 | commit_author:
40 | name: jamestgrant
41 | email: jamesrgrant@mediakind.com
42 | install: |
43 | bin.install "kubectl-debug"
44 | homepage: "https://www.github.com/jamestgrant/kubectl-debug"
45 | description: "Debug a troublesome container using a dubug container which contains all your favorite troubleshooting tools pre-installed and runs in the same cgroup/ipc/network namespace as your troublesome container"
46 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.15.0 as build
2 |
3 | RUN apk add lxcfs containerd
4 |
5 | FROM alpine:3.15.0
6 |
7 | COPY --from=build /usr/bin/lxcfs /usr/bin/lxcfs
8 | COPY --from=build /usr/lib/*fuse* /usr/lib/
9 | COPY --from=build /usr/bin/ctr /usr/bin/ctr
10 |
11 | COPY ./scripts/start.sh /
12 | RUN chmod 755 /start.sh
13 | COPY ./debug-agent /bin/debug-agent
14 |
15 | EXPOSE 10027
16 |
17 | CMD ["/start.sh"]
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build kubectl-debug-binary debug-agent-binary debug-agent-docker-image check
2 |
3 | LDFLAGS = $(shell ./version.sh)
4 | GOENV := GO15VENDOREXPERIMENT="1" GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=amd64
5 | GO := $(GOENV) go
6 |
7 | default: build
8 |
9 | build: kubectl-debug-binary debug-agent-docker-image
10 |
11 | kubectl-debug-binary:
12 | GO111MODULE=on CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o kubectl-debug cmd/kubectl-debug/main.go
13 |
14 | debug-agent-docker-image: debug-agent-binary
15 | docker build . -t jamesgrantmediakind/debug-agent:latest
16 |
17 | debug-agent-binary:
18 | $(GO) build -ldflags '$(LDFLAGS)' -o debug-agent cmd/debug-agent/main.go
19 |
20 | check:
21 | find . -iname '*.go' -type f | grep -v /vendor/ | xargs gofmt -l
22 | GO111MODULE=on go test -v -race ./...
23 | $(GO) vet ./...
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kubectl-debug
2 |
3 | 
4 | [](https://goreportcard.com/report/github.com/jamesTGrant/kubectl-debug)
5 | [](https://hub.docker.com/r/jamesgrantmediakind/debug-agent)
6 |
7 | - [Overview](#overview)
8 | - [Quick start](#quick-start)
9 | - [Download the binary](#download-the-binary)
10 | - [Usage instructions](#usage-instructions)
11 | - [Build from source](#build-from-source)
12 | - [Under the hood](#under-the-hood)
13 | - [Configuration options and overrides](#configuration-options-and-overrides)
14 | - [Authorization / required privileges](#authorization-required-privileges)
15 | - [(Optional) Create a Secret for use with Private Docker Registries](#create-a-secret-for-use-with-private-docker-registries)
16 | - [Roadmap](#roadmap)
17 | - [Contribute](#contribute)
18 | - [Acknowledgement](#acknowledgement)
19 |
20 |
21 | # Overview
22 |
23 | This project is a fork of this fine project: https://github.com/aylei/kubectl-debug which is no longer maintained (hence this fork). The credit for this project belongs with [aylei](https://github.com/aylei). Aylei and I have chatted and we are happy that this project will live on and get maintained here.
24 |
25 | `kubectl-debug` is an 'out-of-tree' solution for connecting to and troubleshooting an existing, running, 'target' container in an existing pod in a Kubernetes cluster.
26 | The target container may have a shell and busybox utils and hence provide some debug capability or it may be very minimal and not even provide a shell - which makes any real-time troubleshooting/debugging very difficult. kubectl-debug is designed to overcome that difficulty.
27 |
28 | There's a short video on YouTube: https://www.youtube.com/watch?v=jJHCxCqPn1g
29 |
30 | How does it work?
31 |
32 |
33 | - User invokes kubectl-debug like this:
kubectl-debug --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME
34 | - kubectl-debug communicates with the cluster using the same interface as kubectl and instructs kubernetes to request the launch of a new 'debug-agent' container on the same node as the 'target' container
35 | - debug-agent process within the debug-agent pod connects directly to containerd (or dockerd if applicable) on the host which is running the 'target' container and requests the launch of a new 'debug' container in the same
pid
, network
, user
and ipc
namespaces as the target container
36 | - In summary: 'kubectl-debug' causes the launch of the 'debug-agent' container, 'debug-agent' the causes the launch of the 'debug' pod/container
37 | - 'debug-agent' pod redirects the terminal output of the 'debug' container to the 'kubectl-debug' executable and so you can interact directly with the shell running in the 'debug' container. You can now use of the troubleshooting tools available in the debug container (BASH, cURL, tcpdump, etc) without the need to have these utilities in the target container image.
38 |
39 |
40 |
41 | `kubectl-debug` is not related to `kubectl debug`
42 |
43 | `kubectl-debug` has been largely replaced by kubernetes [ephemeral containers](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers).
44 | Ephemeral containers feature is in beta (enabled by default) from kubernetes 1.23
45 | Ephemeral containers feature is in alpha from kubernetes 1.16 to 1.22
46 | In Kuberenetes, by default, you are required to explicitly enable alpha features (alpha features are not enabled by default). If you are using Azure AKS (and perhaps others) you are not able, nor permitted, to configure kubernetes feature flags and so you will need a solution like the one provided by this github project.
47 |
48 | # Quick start
49 |
50 | ## Download the binary
51 | (I'm testing Linux only):
52 | ```bash
53 | export RELEASE_VERSION=1.0.0
54 | # linux x86_64
55 | curl -Lo kubectl-debug https://github.com/JamesTGrant/kubectl-debug/releases/download/v${RELEASE_VERSION}/kubectl-debug
56 |
57 | # make the binary executable
58 | chmod +x kubectl-debug
59 |
60 | # run the binary pointing at whatever cluster kubectl points at
61 | ./kubectl-debug --namespace NAMESPACE TARGET_POD_NAME -c TARGET_CONTAINER_NAME
62 | ```
63 | ## Build from source
64 |
65 | Clone this repo and:
66 | ```bash
67 | # to use this kubectl-debug utility, you only need to take the resultant kubectl-debug binary
68 | # file which is created by:
69 | make kubectl-debug-binary
70 |
71 | # to 'install' the kubectl-debug binary, make it executable and either call it directy, put
72 | # it in your PATH, or move it to a location which is already in your PATH:
73 |
74 | chmod +x kubectl-debug
75 | mv kubectl-debug /usr/local/bin
76 |
77 |
78 |
79 | #####################
80 | # Extra options
81 | ######################
82 |
83 | # build 'debug-agent' binary only - you wont need this. This is the binary/executable that
84 | # the 'debug-agent container' contains.
85 | # The dockerfile of the debug-agent container refers to this binary.
86 | make debug-agent-binary
87 |
88 | # build 'debug-agent' binary, and the 'debug-agent docker image'
89 | # a docker image `jamesgrantmediakind/debug-agent:latest` will be created locally
90 | make debug-agent-docker-image
91 |
92 | # make everything; kubectl-debug-binary, debug-agent-binary, and 'debug-agent-docker-image'
93 | make
94 |
95 | ```
96 |
97 | ## Usage instructions
98 |
99 | ```bash
100 | # kubectl 1.12.0 or higher
101 |
102 | # print the help
103 | kubectl-debug -h
104 |
105 | # start the debug container in the same namespace, and cgroup etc as container 'TARGET_CONTAINER_NAME' in
106 | # pod 'POD_NAME' in namespace 'NAMESPACE'
107 | kubectl-debug --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME
108 |
109 | # in case of your pod stuck in `CrashLoopBackoff` state and cannot be connected to,
110 | # you can fork a new pod and diagnose the problem in the forked pod
111 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --fork
112 |
113 | # In 'fork' mode, if you want the copied pod to retain the labels of the original pod, you can use
114 | # the --fork-pod-retain-labels parameter (comma separated, no spaces). If not set (default), this parameter
115 | # is empty and so any labels of the original pod are not retained, and the labels of the copied pods are empty.
116 | # Example of fork mode:
117 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --fork --fork-pod-retain-labels=,,
118 |
119 | # in order to interact with the debug-agent pod on a node which doesn't have a public IP or direct access
120 | # (firewall and other reasons) to access, port-forward mode is enabled by default. If you don't want
121 | # port-forward mode, you can use --port-forward false to turn off it. I don't know why you'd want to do
122 | # this, but you can if you want.
123 | kubectl-debug --port-forward=false --namespace NAMESPACE POD_NAME -c CONTAINER_NAME
124 |
125 | # you can choose a different debug container image. By default, nicolaka/netshoot:latest will be
126 | # used but you can specify anything you like
127 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --image nicolaka/netshoot:latest
128 |
129 | # you can set the debug-agent pod's resource limits/requests, for example:
130 | # default is not set
131 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --agent-pod-cpu-requests=250m --agent-pod-cpu-limits=500m --agent-pod-memory-requests=200Mi --agent-pod-memory-limits=500Mi
132 |
133 | # use primary docker registry, set registry kubernetes secret to pull image
134 | # the default registry-secret-name is kubectl-debug-registry-secret, the default namespace is default
135 | # please set the secret data source as {Username: , Password: }
136 | kubectl-debug --namespace NAMESPACE POD_NAME --image nicolaka/netshoot:latest --registry-secret-name --registry-secret-namespace
137 |
138 | # in addition to passing cli arguments, you can use a config file if you would like to
139 | # non-default values for various things.
140 | kubectl-debug --configfile /PATH/FILENAME --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME
141 |
142 | ```
143 | ## Debugging examples
144 |
145 | This guide shows a few typical example of debugging a target container.
146 |
147 | ### Basic
148 |
149 | When you run `kubectl-debug` it causes a 'debug container' to be created on the same node, and which runs in the same `pid`, `network`, `ipc` and `user` namespace, as the target container.
150 | By default, `kubectl-debug` uses [`nicolaka/netshoot`](https://github.com/nicolaka/netshoot) as container image for the 'debug container'.
151 | The netshoot [project documentation](https://github.com/nicolaka/netshoot/blob/master/README.md) provides excellent guides and examples for using various tools to troubleshoot your target container.
152 |
153 | Here are a few examples to show `netshoot` working with `kubectl-debug`:
154 |
155 | Connect to a running container 'demo-container' in pod 'demo-pod' in the default namespace:
156 |
157 | ```shell
158 | ➜ ~ kubectl-debug --namespace default target-pod -c target-container
159 |
160 | Agent Pod info: [Name:debug-agent-pod-da46a000-8429-11e9-a40c-8c8590147766, Namespace:default, Image:jamesgrantmediakind/debug-agent:latest, HostPort:10027, ContainerPort:10027]
161 | Waiting for pod debug-agent-pod-da46a000-8429-11e9-a40c-8c8590147766 to run...
162 | pod target-pod pod IP: 10.233.111.78, agentPodIP 172.16.4.160
163 | wait for forward port to debug agent ready...
164 | Forwarding from 127.0.0.1:10027 -> 10027
165 | Forwarding from [::1]:10027 -> 10027
166 | Handling connection for 10027
167 | pulling image nicolaka/netshoot:latest...
168 | latest: Pulling from nicolaka/netshoot
169 | Digest: sha256:5b1f5d66c4fa48a931ff54f2f34e5771eff2bc5e615fef441d5858e30e9bb921
170 | Status: Image is up to date for nicolaka/netshoot:latest
171 | starting debug container...
172 | container created, open tty...
173 |
174 | [1] 🐳 → hostname
175 | target-container
176 | ```
177 |
178 |
179 | Navigating the filesystem of the target container:
180 |
181 | The root filesystem of target container is located in `/proc/{pid}/root/`, and the `pid` is typically '1'.
182 | You can `chroot` to the root filesystem of target container to navigate the target container filesystem or
183 | `cd /proc/1/root` works just as well (assuming PID '1' is the correct PID).
184 |
185 | ```shell
186 | root @ /
187 | [2] 🐳 → chroot /proc/1/root
188 |
189 | root @ /
190 | [3] 🐳 → cd /proc/1/root
191 |
192 | root @ /
193 | [#] 🐳 → ls
194 | bin entrypoint.sh home lib64 mnt root sbin sys tmp var
195 | dev etc lib media proc run srv usr
196 | (you can navigate the target containers filesystem and view/edit files)
197 |
198 | root @ /
199 | [#] 🐳 → ./entrypoint.sh
200 | (you can attempt to run the target containers entrypoint.sh script and perhaps see what errors are produced)
201 | ```
202 |
203 |
204 | Using **iftop** to inspect network traffic:
205 | ```shell
206 | root @ /
207 | [4] 🐳 → iftop -i eth0
208 | interface: eth0
209 | IP address is: 10.233.111.78
210 | MAC address is: 86:c3:ae:9d:46:2b
211 | (CLI graph omitted)
212 | ```
213 |
214 |
215 | Using **drill** to diagnose DNS:
216 | ```shell
217 | root @ /
218 | [5] 🐳 → drill -V 5 demo-service
219 | ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
220 | ;; flags: rd ; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
221 | ;; QUESTION SECTION:
222 | ;; demo-service. IN A
223 |
224 | ;; ANSWER SECTION:
225 |
226 | ;; AUTHORITY SECTION:
227 |
228 | ;; ADDITIONAL SECTION:
229 |
230 | ;; Query time: 0 msec
231 | ;; WHEN: Sat Jun 1 05:05:39 2019
232 | ;; MSG SIZE rcvd: 0
233 | ;; ->>HEADER<<- opcode: QUERY, rcode: NXDOMAIN, id: 62711
234 | ;; flags: qr rd ra ; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
235 | ;; QUESTION SECTION:
236 | ;; demo-service. IN A
237 |
238 | ;; ANSWER SECTION:
239 |
240 | ;; AUTHORITY SECTION:
241 | . 30 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019053101 1800 900 604800 86400
242 |
243 | ;; ADDITIONAL SECTION:
244 |
245 | ;; Query time: 58 msec
246 | ;; SERVER: 10.233.0.10
247 | ;; WHEN: Sat Jun 1 05:05:39 2019
248 | ;; MSG SIZE rcvd: 121
249 | ```
250 |
251 | ### `proc` filesystem and FUSE
252 |
253 | It is common to use tools like `top`, `free` to inspect system metrics like CPU usage and memory. Using these commands will display the metrics from the host system by default. Because they read the metrics from the `proc` filesystem (`/proc/*`), which is mounted from the host system. This can be extremely useful (you can still inspect the pod/container metrics of as part of the host metrics) You may find [this blog post](https://fabiokung.com/2014/03/13/memory-inside-linux-containers/) useful.
254 |
255 | ## Debug Pod in "CrashLoopBackoff"
256 |
257 | Troubleshooting kubernetes containers in the `CrashLoopBackoff` state can be tricky. Using kubectl-debug 'normally' probably wont help you as the debug container processed will be terminated reaped once the target container (process with pid 1) exits. To tackle with this, `kubectl-debug` provides the `--fork` flag, which borrows the idea from the `oc debug` command: copy the currently crashing pod and (hopefully) the issue will reproduce in the forked Pod with the added ability to debug via the debug container.
258 |
259 | Under the hood, `kubectl debug --fork` will copy the entire Pod spec and:
260 |
261 | * strip all the labels, so that no traffic will be routed from service to this pod (see[Readme.md](/README.md) for instructions on duplicating the labels);
262 | * modify the entry-point of target container in order to hold the pid namespace and avoid the Pod crash again;
263 |
264 | Here's an example:
265 |
266 | ```shell
267 | ➜ ~ kubectl-debug demo-pod -c demo-container --fork
268 | Agent Pod info: [Name:debug-agent-pod-dea9e7c8-8439-11e9-883a-8c8590147766, Namespace:default, Image:jamesgrantmediakind/debug-agent:latest, HostPort:10027, ContainerPort:10027]
269 | Waiting for pod debug-agent-pod-dea9e7c8-8439-11e9-883a-8c8590147766 to run...
270 | Waiting for pod demo-pod-e23c1b68-8439-11e9-883a-8c8590147766-debug to run...
271 | pod demo-pod PodIP 10.233.111.90, agentPodIP 172.16.4.160
272 | wait for forward port to debug agent ready...
273 | Forwarding from 127.0.0.1:10027 -> 10027
274 | Forwarding from [::1]:10027 -> 10027
275 | Handling connection for 10027
276 | pulling image nicolaka/netshoot:latest...
277 | latest: Pulling from nicolaka/netshoot
278 | Digest: sha256:5b1f5d66c4fa48a931ff54f2f34e5771eff2bc5e615fef441d5858e30e9bb921
279 | Status: Image is up to date for nicolaka/netshoot:latest
280 | starting debug container...
281 | container created, open tty...
282 |
283 | [1] 🐳 → ps -ef
284 | PID USER TIME COMMAND
285 | 1 root 0:00 sh -c -- while true; do sleep 30; done;
286 | 6 root 0:00 sleep 30
287 | 7 root 0:00 /bin/bash -l
288 | 15 root 0:00 ps -ef
289 | ```
290 |
291 |
292 | ## Debug init container
293 |
294 | Just like debugging the ordinary container, we can debug the init-container of a pod. You must specify the container name of init-container:
295 |
296 | ```shell
297 | ➜ ~ kubectl-debug demo-pod -c init-container
298 | ```
299 |
300 |
301 | # Under the hood
302 |
303 | `kubectl-debug` consists of 3 components:
304 |
305 | * the 'kubectl-debug' executable serves the `kubectl-debug` command and interfaces with the kube-api-server
306 | * the 'debug-agent' pod is a temporary pod that is started in the cluster by kubectl-debug. The 'debug-agent' container is responsible for starting and manipulating the 'debug container'. The 'debug-agent' will also act as a websockets relay for remote tty to join the output of the 'debug container' to the terminal from which the kubectl-debug command was issued
307 | * the 'debug container' which is the container that provides the debugging utilities and the shell in which the human user performs their debugging activity. `kubectl-debug` doesn't provide this - it's an 'off-the-shelf container image (nicolaka/netshoot:latest by default), it is invoked and configured by 'debug-agent'.
308 |
309 | The following occurs when the user runs the command: `kubectl-debug --namespace -c `
310 |
311 | 1. 'kubectl-debug' gets the target-pod info from kube-api-server and extracts the `host` information (the target-pod within the namespace )
312 | 2. 'kubectl-debug' sends a 'debug-agent' pod specification to kube-api-server with a node-selector matching the `host`. By default the container image is `docker.io/jamesgrantmediakind/debug-agent:latest`
313 | 3. kube-api-server requests the creation of 'debug-agent' pod. 'debug-agent' pod is created in the default namespace (doesn't have to be the same namespace as the target pod)
314 | 4. 'kubectl-debug' sends an HTTP request to the 'debug-agent' pod running on the `host` which includes a protocol upgrade from HTTP to SPDY
315 | 5. debug-agent' checks if the target container is actively running, if not, write an error to client
316 | 6. 'debug-agent' interfaces with containerd (or dockerd if applicable) on the host to request the creation of the 'debug-container'. `debug container` with `tty` and `stdin` opened, the 'debug-agent' configures the `debug container`'s `pid`, `network`, `ipc` and `user` namespace to be that of the target container
317 | 7. 'debug-agent' pipes the connection into the `debug container` using `attach`
318 | 8. Human performs debugging/troubleshooting on the target container from 'within' in the debug container with access to the target container process/network/ipc namespaces and root filesystem
319 | 9. debugging complete, user exits the debug-container shell which closes the SPDY connection
320 | 10. 'debug-agent' closes the SPDY connection, then waits for the `debug container` to exit and do the cleanup
321 | 11. 'debug-agent' pod is deleted
322 |
323 |
324 | # Configuration options and overrides
325 |
326 | The `debug-agent` uses [nicolaka/netshoot](https://github.com/nicolaka/netshoot) as the default image to run debug container, and uses `bash` as default entrypoint. You can override the default image and entrypoint, as well as a number of other useful things, by passing the config file to the kubectl-debug command like this:
327 | ```bash
328 | kubectl-debug --configfile CONFIGFILE --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME
329 | ```
330 | Example configfile:
331 | ```yaml
332 | # debug agent listening port (outside container)
333 | # default: 10027
334 | agentPort: 10027
335 |
336 | # namespace of debug-agent pod (does'nt need to be in the same namespace as the target container)
337 | # default: 'default'
338 | agentPodNamespace: default
339 |
340 | # prefix of debug-agent pod
341 | # default: 'debug-agent-pod'
342 | agentPodNamePrefix: debug-agent-pod
343 |
344 | # image of debug-agent pod
345 | # default: jamesgrantmediakind/debug-agent:latest
346 | agentImage: jamesgrantmediakind/debug-agent:latest
347 |
348 | # auditing can be enabled by setting 'audit' to 'true'
349 | # default: false
350 | audit: false
351 |
352 | # whether using port-forward when connecting debug-agent
353 | # default: true
354 | portForward: true
355 |
356 | # the 'debug container' image
357 | # default: nicolaka/netshoot:latest
358 | # for most reliable result, use the full path - for example: docker.io/library/busybox:latest will
359 | # work but busybox:latest may not (depending on the cluster)
360 | image: nicolaka/netshoot:latest
361 |
362 | # start command of the debug container
363 | # `kubectl-debug` always specifies this explicitly and you can not override this without making code changes to `kubectl-debug`) this is by design.
364 | # default ['bash']
365 | command:
366 | - '/bin/bash'
367 | - '-l'
368 |
369 | # private docker registry auth kubernetes secret
370 | # default registrySecretName: kubectl-debug-registry-secret
371 | # default registrySecretNamespace: default
372 | registrySecretName: my-debug-secret
373 | registrySecretNamespace: debug
374 |
375 | # you can set the agent pod's resource limits/requests:
376 | # default is not set
377 | agentCpuRequests: ""
378 | agentCpuLimits: ""
379 | agentMemoryRequests: ""
380 | agentMemoryLimits: ""
381 |
382 | # in fork mode, if you want the copied pod retains the labels of the original pod, you can change this params
383 | # format is []string
384 | # If not set, this parameter is empty by default (Means that any labels of the original pod are not retained, and the labels of the copied pods are empty.)
385 | forkPodRetainLabels: []
386 |
387 | # You can disable SSL certificate check when communicating with image registry by
388 | # setting registrySkipTLSVerify to true.
389 | registrySkipTLSVerify: false
390 |
391 | # You can set the debug logging output level with the verbosity setting. There are two levels of verbosity, 0 and any positive integer (ie; 'verbosity : 1' will produce the same debug output as 'verbosity : 5')
392 | verbosity : 0
393 | ```
394 |
395 | # Authorization / required privileges
396 |
397 | Put simply - if you can successfully issue the command `kubectl exec` to a container in your cluster then `kubectl-debug` will work for you!
398 | Detail: `kubectl-debug` reuses the privilege of the `pod/exec` sub-resource to do authorization, which means that it has the same privilege requirements as the `kubectl exec` command.
399 |
400 | The processes in the debug-agent container run as `root` and the debug-agent container `securityContext` is configured with `privileged: true` Some clusters such as OpenShift may not, by default, allow either of these practices.
401 |
402 | # Auditing / Security
403 |
404 | Some teams may want to limit what debug image users are allowed to use and to have an audit record for each command they run in the debug container.
405 |
406 | You can add ```KCTLDBG_RESTRICT_IMAGE_TO``` to the config file to restrict the debug-agent to use a specific container image. For example putting the following in the config file will force the agent to always use the image ```docker.io/nicolaka/netshoot:latest``` regardless of what the user specifies on the kubectl-debug command line. This may be helpful for restrictive environments that mandate the use of ```KCTLDBG_RESTRICT_IMAGE_TO```
407 | ```
408 | KCTLDBG_RESTRICT_IMAGE_TO: docker.io/nicolaka/netshoot:latest
409 | ```
410 | If ```KCTLDBG_RESTRICT_IMAGE_TO``` is set and as a result agent is using an image that is different than what the user requested then the agent will log to standard out a message that announces what is happening. The message will include the URI's of both images.
411 |
412 | There are 3 settings related to auditing.
413 |
414 | audit
415 | - Boolean value that indicates whether auditing should be enabled or not. Default value is
false
416 | audit_fifo
417 | - Template of path to a FIFO that will be used to exchange audit information from the debug container to the agent. The default value is
/var/data/kubectl-debug-audit-fifo/KCTLDBG-CONTAINER-ID
. If auditing is enabled then the agent will :
418 |
419 | - Prior to creating the debug container, create a fifo based on the value of
audit_fifo
. The agent will replace KCTLDBG-CONTAINER-ID
with the id of the debug container it is creating.
420 | - Create a thread that reads lines of text from the FIFO and then writes log messages to standard out, where the log messages look similar to example below
421 |
422 | 2020/05/22 17:59:58 runtime.go:717: audit - user: USERNAME/885cbd0506868985a6fc491bb59a2d3c debugee: 48107cbdacf4b478cbf1e2e34dbea6ebb48a2942c5f3d1effbacf0a216eac94f exec: 265 execve("/bin/tar", ["tar", "--help"], 0x55a8d0dfa6c0 /* 7 vars */) = 0
423 |
424 | Where USERNAME is the kubernetes user as determined by the client that launched the debug container and debuggee is the container id of the container being debugged.
425 |
426 | - Bind mount the fifo it creates to the debugger container.
427 |
428 |
429 | audit_shim
430 | - String array that will be placed before the command that will be run in the debug container. The default value is
{"/usr/bin/strace", "-o", "KCTLDBG-FIFO", "-f", "-e", "trace=/exec"}
. The agent will replace KCTLDBG-FIFO with the fifo path ( see above ) If auditing is enabled then agent will use the concatenation of the array specified by audit_shim
and the original command array it was going to use.
431 |
432 |
433 | # (Optional) Create a Secret for Use with Private Docker Registries
434 |
435 | You can use a new or existing [Kubernetes `dockerconfigjson` secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). For example:
436 |
437 | ```bash
438 | # Be sure to run "docker login" beforehand.
439 | kubectl create secret generic kubectl-debug-registry-secret \
440 | --from-file=.dockerconfigjson= \
441 | --type=kubernetes.io/dockerconfigjson
442 | ```
443 |
444 | Alternatively, you can create a secret with the key `authStr` and a JSON payload containing a `Username` and `Password`. For example:
445 |
446 | ```bash
447 | echo -n '{"Username": "calmkart", "Password": "calmkart"}' > ./authStr
448 | kubectl create secret generic kubectl-debug-registry-secret --from-file=./authStr
449 | ```
450 |
451 | Refer to [the official Kubernetes documentation on Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) for more ways to create them.
452 |
453 |
454 | # Roadmap
455 |
456 | Jan '22 - plan to add support for k3s enviroments
457 | March '22 - actually add support for k3s enviroments and auto LXCFS detection handling
458 |
459 | `kubectl-debug` has been replaced by kubernetes [ephemeral containers](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers).
460 | Ephemeral containers feature is in beta from kubernetes 1.23
461 | Ephemeral containers feature is in alpha from kubernetes 1.16 to 1.22
462 |
463 | In Kuberenetes, by default, you are required to explicitly enable alpha features (alpha features are not enabled by default). If you are using Azure AKS (and perhaps others) you are not able, nor permitted, to configure kubernetes feature flags and so you will need a solution like the one provided by this github project.
464 |
465 |
466 | # Contribute
467 |
468 | Feel free to open issues and pull requests. Any feedback is much appreciated!
469 |
470 | # Acknowledgement
471 |
472 | This project is a fork of [this project](https://github.com/aylei/kubectl-debug) which is no longer maintained (hence this fork).
473 | This project would not be here without the effort of [aylei contributors](https://github.com/aylei/kubectl-debug/graphs/contributors) and [this fork](https://github.com/JamesTGrant/kubectl-debug/graphs/contributors)
474 |
--------------------------------------------------------------------------------
/cmd/debug-agent/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "github.com/jamestgrant/kubectl-debug/pkg/debug-agent"
6 | "log"
7 | )
8 |
9 | func main() {
10 | log.SetFlags(log.LstdFlags | log.Lshortfile)
11 | var configFile string
12 | flag.StringVar(&configFile, "config.file", "", "Config file location.")
13 | flag.Parse()
14 |
15 | config, err := debugagent.LoadFile(configFile)
16 | if err != nil {
17 | log.Fatalf("error reading config %v", err)
18 | }
19 |
20 | server, err := debugagent.NewServer(config)
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 |
25 | if err := server.Run(); err != nil {
26 | log.Fatal(err)
27 | }
28 |
29 | log.Println("server stopped, see you next time!")
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/kubectl-debug/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/jamestgrant/kubectl-debug/pkg/kubectl-debug"
5 | "github.com/spf13/pflag"
6 | "k8s.io/cli-runtime/pkg/genericclioptions"
7 | "os"
8 | )
9 |
10 | func main() {
11 | flags := pflag.NewFlagSet("kubectldebug", pflag.ExitOnError)
12 | pflag.CommandLine = flags
13 |
14 | // bypass to DebugCmd
15 | cmd := kubectldebug.NewDebugCmd(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
16 | if err := cmd.Execute(); err != nil {
17 | os.Exit(1)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/jamestgrant/kubectl-debug
2 |
3 | go 1.12
4 |
5 | require (
6 | cloud.google.com/go v0.38.0
7 | contrib.go.opencensus.io/exporter/ocagent v0.6.0
8 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
9 | github.com/Azure/go-autorest v11.2.8+incompatible
10 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e
11 | github.com/Microsoft/go-winio v0.4.11
12 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5
13 | github.com/PuerkitoBio/purell v1.1.1
14 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
15 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
16 | github.com/census-instrumentation/opencensus-proto v0.2.1
17 | github.com/containerd/cgroups v0.0.0-20200407151229-7fc7a507c04c // indirect
18 | github.com/containerd/console v1.0.0 // indirect
19 | github.com/containerd/containerd v1.3.3
20 | github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect
21 | github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect
22 | github.com/containerd/ttrpc v1.0.0 // indirect
23 | github.com/containerd/typeurl v1.0.0
24 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
25 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible // 2020-04-15 d : https://github.com/containerd/containerd/issues/3031
26 | github.com/docker/docker v0.0.0-20171023200535-7848b8beb9d3
27 | github.com/docker/go-connections v0.4.0
28 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
29 | github.com/docker/go-units v0.4.0
30 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c
31 | github.com/evanphx/json-patch v4.1.0+incompatible
32 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
33 | github.com/go-openapi/jsonpointer v0.19.0
34 | github.com/go-openapi/jsonreference v0.19.0
35 | github.com/go-openapi/spec v0.19.0
36 | github.com/go-openapi/swag v0.19.0
37 | github.com/gogo/googleapis v1.3.2 // indirect
38 | github.com/gogo/protobuf v1.3.1
39 | github.com/golang/protobuf v1.3.2
40 | github.com/google/btree v1.0.0
41 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
42 | github.com/google/uuid v1.1.1
43 | github.com/googleapis/gnostic v0.2.0
44 | github.com/gophercloud/gophercloud v0.0.0-20181221023737-94924357ebf6
45 | github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f
46 | github.com/imdario/mergo v0.3.6
47 | github.com/inconshreveable/mousetrap v1.0.0
48 | github.com/json-iterator/go v1.1.5
49 | github.com/konsorten/go-windows-terminal-sequences v1.0.2
50 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe
51 | github.com/matttproud/golang_protobuf_extensions v1.0.1
52 | github.com/mitchellh/go-wordwrap v1.0.0
53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
54 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
55 | github.com/opencontainers/go-digest v1.0.0-rc1
56 | github.com/opencontainers/image-spec v1.0.1
57 | github.com/opencontainers/runc v0.1.1 // indirect
58 | github.com/opencontainers/runtime-spec v1.0.2
59 | github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709
60 | github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c
61 | github.com/peterbourgon/diskv v2.0.1+incompatible
62 | github.com/pkg/errors v0.9.1
63 | github.com/prometheus/client_golang v0.9.2
64 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
65 | github.com/prometheus/common v0.0.0-20181218105931-67670fe90761
66 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a
67 | github.com/rs/xid v1.3.0
68 | github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5
69 | github.com/shurcooL/sanitized_anchor_name v1.0.0
70 | github.com/sirupsen/logrus v1.4.2
71 | github.com/spf13/cobra v0.0.3
72 | github.com/spf13/pflag v1.0.3
73 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
74 | github.com/urfave/cli v1.22.4 // indirect
75 | go.opencensus.io v0.22.0
76 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
77 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7
78 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
79 | golang.org/x/sync v0.0.0-20190423024810-112230192c58
80 | golang.org/x/sys v0.0.0-20200120151820-655fe14d7479
81 | golang.org/x/text v0.3.2
82 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c
83 | google.golang.org/api v0.7.0
84 | google.golang.org/appengine v1.5.0
85 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610
86 | google.golang.org/grpc v1.22.0
87 | gopkg.in/inf.v0 v0.9.1
88 | gopkg.in/yaml.v2 v2.2.2
89 | k8s.io/api v0.0.0-20181130031204-d04500c8c3dd
90 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628
91 | k8s.io/apiserver v0.0.0-20181220070914-ce7b605bead3
92 | k8s.io/cli-runtime v0.0.0-20190228180923-a9e421a79326
93 | k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4
94 | k8s.io/klog v0.1.0
95 | k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580
96 | k8s.io/kubernetes v1.13.1
97 | k8s.io/utils v0.0.0-20181115163542-0d26856f57b3
98 | sigs.k8s.io/yaml v1.1.0
99 | )
100 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
4 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
5 | cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
6 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
7 | contrib.go.opencensus.io/exporter/ocagent v0.2.0 h1:Q/jXnVbliDYozuWJni9452xsSUuo+y8yrioxRgofBhE=
8 | contrib.go.opencensus.io/exporter/ocagent v0.2.0/go.mod h1:0fnkYHF+ORKj7HWzOExKkUHeFX79gXSKUQbpnAM+wzo=
9 | contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM=
10 | contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
11 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
12 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
13 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
14 | github.com/Azure/go-autorest v11.2.8+incompatible h1:Q2feRPMlcfVcqz3pF87PJzkm5lZrL+x6BDtzhODzNJM=
15 | github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
16 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
17 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA=
18 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
19 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
20 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
21 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
22 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
23 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
24 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
25 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
26 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
28 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
29 | github.com/census-instrumentation/opencensus-proto v0.0.2-0.20180913191712-f303ae3f8d6a/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
30 | github.com/census-instrumentation/opencensus-proto v0.1.0 h1:VwZ9smxzX8u14/125wHIX7ARV+YhR+L4JADswwxWK0Y=
31 | github.com/census-instrumentation/opencensus-proto v0.1.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
32 | github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
33 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
34 | github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
35 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
36 | github.com/containerd/cgroups v0.0.0-20200407151229-7fc7a507c04c h1:BRbZO594sFDSfkqApcikeNRjePj+rJNoh4waZgefcEE=
37 | github.com/containerd/cgroups v0.0.0-20200407151229-7fc7a507c04c/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
38 | github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
39 | github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
40 | github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
41 | github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
42 | github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI=
43 | github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk=
44 | github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
45 | github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b h1:qUtCegLdOUVfVJOw+KDg6eJyE1TGvLlkGEd1091kSSQ=
46 | github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
47 | github.com/containerd/ttrpc v1.0.0 h1:NY8Zk2i7TpkLxrkOASo+KTFq9iNCEmMH2/ZG9OuOw6k=
48 | github.com/containerd/ttrpc v1.0.0/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
49 | github.com/containerd/typeurl v1.0.0 h1:7LMH7LfEmpWeCkGcIputvd4P0Rnd0LrIv1Jk2s5oobs=
50 | github.com/containerd/typeurl v1.0.0/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
51 | github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
52 | github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
53 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
54 | github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
55 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
56 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
58 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
59 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
60 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
61 | github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU=
62 | github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
63 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
64 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
65 | github.com/docker/docker v0.0.0-20171023200535-7848b8beb9d3 h1:EVdfrzOhgGwYz/l0c52j97khuNSZTk4J0q3HHri/aUc=
66 | github.com/docker/docker v0.0.0-20171023200535-7848b8beb9d3/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
67 | github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
68 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
69 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
70 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
71 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
72 | github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
73 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
74 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
75 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
76 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
77 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
78 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
79 | github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
80 | github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
81 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
82 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
83 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
84 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
85 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
86 | github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
87 | github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
88 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
89 | github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
90 | github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
91 | github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
92 | github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
93 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
94 | github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
95 | github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
96 | github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
97 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
98 | github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c=
99 | github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
100 | github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
101 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
102 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
103 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
104 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
105 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
106 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
107 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
108 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
109 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
110 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
111 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
112 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
113 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
114 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
115 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
116 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
117 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
118 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
119 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
120 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
121 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
122 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
123 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
124 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
125 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
126 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
127 | github.com/gophercloud/gophercloud v0.0.0-20181221023737-94924357ebf6 h1:4/heZnxONZh0s2O5wg6R0nPBs2ikXQ8nHKGuSBNWqx8=
128 | github.com/gophercloud/gophercloud v0.0.0-20181221023737-94924357ebf6/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
129 | github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM=
130 | github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
131 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
132 | github.com/grpc-ecosystem/grpc-gateway v1.9.4 h1:5xLhQjsk4zqPf9EHCrja2qFZMx+yBqkO3XgJ14bNnU0=
133 | github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
134 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
135 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
136 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
137 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
138 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
139 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
140 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
141 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
142 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
143 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
144 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
145 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
146 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
147 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
148 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
149 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
150 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
151 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
152 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
153 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
154 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
155 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
156 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w=
157 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
158 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
159 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
160 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
161 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
162 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
163 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
164 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
165 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
166 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
167 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
168 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
169 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
170 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
171 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
172 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
173 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
174 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
175 | github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
176 | github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
177 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
178 | github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 h1:zNBQb37RGLmJybyMcs983HfUfpkw9OTFD9tbBfAViHE=
179 | github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
180 | github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
181 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
182 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
183 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
184 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
185 | github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
186 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
187 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
188 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
189 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
190 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
191 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
192 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
193 | github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
194 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
195 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
196 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
197 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
198 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
199 | github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 h1:z6tvbDJ5OLJ48FFmnksv04a78maSTRBUIhkdHYV5Y98=
200 | github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
201 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
202 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
203 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
204 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
205 | github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
206 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
207 | github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5 h1:+6eORf9Bt4C3Wjt91epyu6wvLW+P6+AEODb6uKgO+4g=
208 | github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
209 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
210 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
211 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
212 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
213 | github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
214 | github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg=
215 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
216 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
217 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
218 | github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
219 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
220 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
221 | github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
222 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
223 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
224 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
225 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
226 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
227 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
228 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
229 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
230 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
231 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
232 | github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
233 | github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
234 | go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI=
235 | go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
236 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
237 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
238 | go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
239 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
240 | golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
241 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
242 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
243 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
244 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
245 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
246 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
247 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
248 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
249 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
250 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
251 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
252 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
253 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
254 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
255 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
256 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
257 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
259 | golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
260 | golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
261 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
262 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
263 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
265 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
266 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
267 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
268 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
269 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
270 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
271 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
272 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
273 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
274 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
275 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
276 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
277 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
278 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
279 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
280 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
281 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
282 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
283 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
284 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
285 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
286 | golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
287 | golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
288 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
289 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
293 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
294 | golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
295 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
296 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
297 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
298 | golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 h1:LhLiKguPgZL+Tglay4GhVtfF0kb8cvOJ0dHTCBO8YNI=
299 | golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
300 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
301 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
302 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
303 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
304 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
305 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
306 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
307 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
308 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
309 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
310 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
311 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
312 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
313 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
314 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
315 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
316 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
317 | google.golang.org/api v0.0.0-20181221000618-65a46cafb132 h1:SLcC5l+3o5vwvXAbdm936WwLkHteUZpo1RULZD7YvQ4=
318 | google.golang.org/api v0.0.0-20181221000618-65a46cafb132/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
319 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
320 | google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
321 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
322 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
323 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
324 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
325 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
326 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
327 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
328 | google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f h1:eT3B0O2ghdSPzjAOznr3oOLyN1HFeYUncYl7FRwg4VI=
329 | google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
330 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
331 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
332 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
333 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
334 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU=
335 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
336 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
337 | google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
338 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
339 | google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
340 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
341 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
342 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
343 | google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
344 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
345 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
346 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
347 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
348 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
349 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
350 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
351 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
352 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
353 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
354 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
355 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
356 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
357 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
358 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
359 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
360 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
361 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
362 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
363 | k8s.io/api v0.0.0-20181130031204-d04500c8c3dd h1:5aHsneN62ehs/tdtS9tWZlhVk68V7yms/Qw7nsGmvCA=
364 | k8s.io/api v0.0.0-20181130031204-d04500c8c3dd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
365 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg=
366 | k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
367 | k8s.io/apimachinery v0.18.1 h1:hKPYcQRPLQoG2e7fKkVl0YCvm9TBefXTfGILa9vjVVk=
368 | k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
369 | k8s.io/apiserver v0.0.0-20181220070914-ce7b605bead3 h1:GIl6hq5bRA5OSLauW8qCPfibG3m+FKtdEGvX36n8/m8=
370 | k8s.io/apiserver v0.0.0-20181220070914-ce7b605bead3/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w=
371 | k8s.io/cli-runtime v0.0.0-20190228180923-a9e421a79326 h1:uMY/EHQt0VHYgRMPWwPl/vQ0K0fPlt5zxTEGAdt8pDg=
372 | k8s.io/cli-runtime v0.0.0-20190228180923-a9e421a79326/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM=
373 | k8s.io/cli-runtime v0.18.2 h1:JiTN5RgkFNTiMxHBRyrl6n26yKWAuNRlei1ZJALUmC8=
374 | k8s.io/cli-runtime v0.18.3 h1:8IBtaTYmXiXipKdx2FAKotvnQMjcF0kSLvX4szY340c=
375 | k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4 h1:aE8wOCKuoRs2aU0OP/Rz8SXiAB0FTTku3VtGhhrkSmc=
376 | k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
377 | k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
378 | k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
379 | k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk=
380 | k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
381 | k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580 h1:fq0ZXW/BAIFZH+dazlups6JTVdwzRo5d9riFA103yuQ=
382 | k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
383 | k8s.io/kubernetes v1.13.1 h1:IwCCcPOZwY9rKcQyBJYXAE4Wgma4oOW5NYR3HXKFfZ8=
384 | k8s.io/kubernetes v1.13.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
385 | k8s.io/kubernetes v1.18.1 h1:qW6zgSY96X/NO+ueIIfsFSbHl/e0Txio62XcA4cxpIk=
386 | k8s.io/kubernetes v1.18.2 h1:37sJPq6p+gx5hEHQSwCWXIiXDc9AajzV1A5UrswnDq0=
387 | k8s.io/utils v0.0.0-20181115163542-0d26856f57b3 h1:S3/Kq185JnolOEemhmDXXd23l2t4bX5hPQPQPADlF1E=
388 | k8s.io/utils v0.0.0-20181115163542-0d26856f57b3/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
389 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
390 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
391 |
--------------------------------------------------------------------------------
/pkg/debug-agent/config.go:
--------------------------------------------------------------------------------
1 | package debugagent
2 |
3 | import (
4 | "fmt"
5 | "gopkg.in/yaml.v2"
6 | "io/ioutil"
7 | "time"
8 | )
9 |
10 | var (
11 | DefaultConfig = Config{
12 | DockerEndpoint: "unix:///var/run/docker.sock",
13 | ContainerdEndpoint: "/run/containerd/containerd.sock",
14 | RuntimeTimeout: 30 * time.Second,
15 | StreamIdleTimeout: 10 * time.Minute,
16 | StreamCreationTimeout: 15 * time.Second,
17 | ListenAddress: "0.0.0.0:10027",
18 | AuditFifo: "/var/data/kubectl-debug-audit-fifo/KCTLDBG-CONTAINER-ID",
19 | AuditShim: []string{"/usr/bin/strace", "-o", "KCTLDBG-FIFO", "-f", "-e", "trace=/exec"},
20 | }
21 | )
22 |
23 | type Config struct {
24 | DockerEndpoint string `yaml:"docker_endpoint,omitempty"`
25 | ContainerdEndpoint string `yaml:"containerd_endpoint,omitempty"`
26 | RuntimeTimeout time.Duration `yaml:"runtime_timeout,omitempty"`
27 | StreamIdleTimeout time.Duration `yaml:"stream_idle_timeout,omitempty"`
28 | StreamCreationTimeout time.Duration `yaml:"stream_creation_timeout,omitempty"`
29 | ListenAddress string `yaml:"listen_address,omitempty"`
30 | Verbosity int `yaml:"verbosity,omitempty"`
31 | Audit bool `yaml:"audit,omitempty"`
32 | AuditFifo string `yaml:"audit_fifo,omitempty"`
33 | AuditShim []string `yaml:"audit_shim,omitempty"`
34 | }
35 |
36 | func Load(s string) (*Config, error) {
37 | cfg := &Config{}
38 | // If the entire config body is empty the UnmarshalYAML method is
39 | // never called. We thus have to set the DefaultConfig at the entry
40 | // point as well.
41 | *cfg = DefaultConfig
42 |
43 | err := yaml.UnmarshalStrict([]byte(s), cfg)
44 | fmt.Printf("Config after reading from file %v\r\n", cfg)
45 | if err != nil {
46 | return nil, err
47 | }
48 | return cfg, nil
49 | }
50 |
51 | func LoadFile(filename string) (*Config, error) {
52 | if len(filename) < 1 {
53 | fmt.Println("No config file provided. Using default values.\r\n")
54 | return &DefaultConfig, nil
55 | }
56 | fmt.Printf("Reading config file %v.\r\n", filename)
57 | c, err := ioutil.ReadFile(filename)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return Load(string(c))
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/debug-agent/lxcfs.go:
--------------------------------------------------------------------------------
1 | package debugagent
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 | }
63 |
--------------------------------------------------------------------------------
/pkg/debug-agent/resize.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2015 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package debugagent
18 |
19 | import (
20 | "k8s.io/apimachinery/pkg/util/runtime"
21 | "k8s.io/client-go/tools/remotecommand"
22 | )
23 |
24 | // handleResizing spawns a goroutine that processes the resize channel, calling resizeFunc for each
25 | // remotecommand.TerminalSize received from the channel. The resize channel must be closed elsewhere to stop the
26 | // goroutine.
27 | func HandleResizing(resize <-chan remotecommand.TerminalSize, resizeFunc func(size remotecommand.TerminalSize)) {
28 | go func() {
29 | defer runtime.HandleCrash()
30 |
31 | for size := range resize {
32 | if size.Height < 1 || size.Width < 1 {
33 | continue
34 | }
35 | resizeFunc(size)
36 | }
37 | }()
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/debug-agent/runtime.go:
--------------------------------------------------------------------------------
1 | package debugagent
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "crypto/tls"
7 | "encoding/base64"
8 | "encoding/json"
9 | "errors"
10 | "fmt"
11 | "io"
12 | "io/ioutil"
13 | "log"
14 | "net"
15 | "net/http"
16 | "net/url"
17 | "os"
18 | "path"
19 | "strings"
20 | "sync"
21 | "syscall"
22 | "text/tabwriter"
23 | "time"
24 |
25 | containerd "github.com/containerd/containerd"
26 | "github.com/containerd/containerd/cio"
27 | "github.com/containerd/containerd/content"
28 | "github.com/containerd/containerd/errdefs"
29 | glog "github.com/containerd/containerd/log"
30 | "github.com/containerd/containerd/namespaces"
31 | "github.com/containerd/containerd/oci"
32 | "github.com/containerd/containerd/pkg/progress"
33 | "github.com/containerd/containerd/remotes"
34 | "github.com/containerd/containerd/remotes/docker"
35 | "github.com/containerd/typeurl"
36 | "github.com/docker/docker/api/types"
37 | "github.com/docker/docker/api/types/container"
38 | "github.com/docker/docker/api/types/strslice"
39 | dockerclient "github.com/docker/docker/client"
40 | "github.com/docker/docker/pkg/stdcopy"
41 | "github.com/google/uuid"
42 | "github.com/jamestgrant/kubectl-debug/pkg/nsenter"
43 | term "github.com/jamestgrant/kubectl-debug/pkg/util"
44 | "github.com/opencontainers/go-digest"
45 | ocispec "github.com/opencontainers/image-spec/specs-go/v1"
46 | "github.com/opencontainers/runtime-spec/specs-go"
47 | kubetype "k8s.io/apimachinery/pkg/types"
48 | "k8s.io/client-go/tools/remotecommand"
49 | kubeletremote "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
50 | )
51 |
52 | type ContainerRuntimeScheme string
53 |
54 | const (
55 | DockerScheme ContainerRuntimeScheme = "docker"
56 | ContainerdScheme ContainerRuntimeScheme = "containerd"
57 | KubectlDebugNS string = "kctldbg"
58 | K8NS string = "k8s.io"
59 | )
60 |
61 | type ContainerInfo struct {
62 | Pid int64
63 | MountDestinations []string
64 | }
65 |
66 | type RunConfig struct {
67 | context context.Context
68 | timeout time.Duration
69 | idOfContainerToDebug string
70 | image string
71 | command []string
72 | stdin io.Reader
73 | stdout io.WriteCloser
74 | stderr io.WriteCloser
75 | tty bool
76 | resize <-chan remotecommand.TerminalSize
77 | clientHostName string
78 | clientUserName string
79 | verbosity int
80 | audit bool
81 | auditFifo string
82 | auditShim []string
83 | }
84 |
85 | func (c *RunConfig) getContextWithTimeout() (context.Context, context.CancelFunc) {
86 | return context.WithTimeout(c.context, c.timeout)
87 | }
88 |
89 | type ContainerRuntime interface {
90 | PullImage(ctx context.Context, image string,
91 | skipTLS bool, authStr string,
92 | cfg RunConfig) error
93 | ContainerInfo(ctx context.Context, cfg RunConfig) (ContainerInfo, error)
94 | RunDebugContainer(cfg RunConfig) error
95 | }
96 |
97 | type DockerContainerRuntime struct {
98 | client *dockerclient.Client
99 | }
100 |
101 | var DockerContainerRuntimeImplementsContainerRuntime ContainerRuntime = (*DockerContainerRuntime)(nil)
102 |
103 | func (c *DockerContainerRuntime) PullImage(ctx context.Context,
104 | image string, skipTLS bool, authStr string,
105 | cfg RunConfig) error {
106 | // Verify that we have a valid Docker AuthConfig and try to make one out of a
107 | // username:password-style authStr if not.
108 | if authStr != "" {
109 | var authConfig types.AuthConfig
110 | authBytes := []byte(authStr)
111 | if err := json.Unmarshal(authBytes, &authConfig); err != nil {
112 | crds := strings.Split(authStr, ":")
113 | if len(crds) == 2 {
114 | authConfig = types.AuthConfig{
115 | Username: crds[0],
116 | Password: crds[1],
117 | }
118 | authBytes, _ = json.Marshal(authConfig)
119 | } else {
120 | return fmt.Errorf("Failed to parse authStr '%s': %v", authStr, err)
121 | }
122 | }
123 | authStr = base64.URLEncoding.EncodeToString(authBytes)
124 | }
125 | out, err := c.client.ImagePull(ctx, image, types.ImagePullOptions{RegistryAuth: authStr})
126 | if err != nil {
127 | return err
128 | }
129 | defer out.Close()
130 | // write pull progress to user
131 | if cfg.verbosity > 0 {
132 | term.DisplayJSONMessagesStream(out, cfg.stdout, 1, true, nil)
133 | }
134 | return nil
135 | }
136 |
137 | func (c *DockerContainerRuntime) ContainerInfo(ctx context.Context, cfg RunConfig) (ContainerInfo, error) {
138 | var ret ContainerInfo
139 | cntnr, err := c.client.ContainerInspect(ctx, cfg.idOfContainerToDebug)
140 | if err != nil {
141 | return ContainerInfo{}, err
142 | }
143 | ret.Pid = int64(cntnr.State.Pid)
144 | for _, mount := range cntnr.Mounts {
145 | ret.MountDestinations = append(ret.MountDestinations, mount.Destination)
146 | }
147 | return ret, nil
148 | }
149 |
150 | func (c *DockerContainerRuntime) RunDebugContainer(cfg RunConfig) error {
151 |
152 | createdBody, err := c.CreateContainer(cfg)
153 | if err != nil {
154 | return err
155 | }
156 | if err := c.StartContainer(cfg, createdBody.ID); err != nil {
157 | return err
158 | }
159 |
160 | defer c.CleanContainer(cfg, createdBody.ID)
161 |
162 | cfg.stdout.Write([]byte("container created, open tty...\n\r"))
163 |
164 | // from now on, should pipe stdin to the container and no long read stdin
165 | // close(m.stopListenEOF)
166 |
167 | return c.AttachToContainer(cfg, createdBody.ID)
168 | }
169 |
170 | func (c *DockerContainerRuntime) CreateContainer(cfg RunConfig) (*container.ContainerCreateCreatedBody, error) {
171 |
172 | config := &container.Config{
173 | Entrypoint: strslice.StrSlice(cfg.command),
174 | Image: cfg.image,
175 | Tty: true,
176 | OpenStdin: true,
177 | StdinOnce: true,
178 | }
179 | hostConfig := &container.HostConfig{
180 | NetworkMode: container.NetworkMode(c.containerMode(cfg.idOfContainerToDebug)),
181 | UsernsMode: container.UsernsMode(c.containerMode(cfg.idOfContainerToDebug)),
182 | IpcMode: container.IpcMode(c.containerMode(cfg.idOfContainerToDebug)),
183 | PidMode: container.PidMode(c.containerMode(cfg.idOfContainerToDebug)),
184 | CapAdd: strslice.StrSlice([]string{"SYS_PTRACE", "SYS_ADMIN"}),
185 | }
186 | ctx, cancel := cfg.getContextWithTimeout()
187 | defer cancel()
188 | body, err := c.client.ContainerCreate(ctx, config, hostConfig, nil, "")
189 | if err != nil {
190 | return nil, err
191 | }
192 | return &body, nil
193 | }
194 |
195 | func (c *DockerContainerRuntime) containerMode(idOfCntnrToDbg string) string {
196 | return fmt.Sprintf("container:%s", idOfCntnrToDbg)
197 | }
198 |
199 | // Run a new container, this container will join the network,
200 | // mount, and pid namespace of the given container
201 | func (c *DockerContainerRuntime) StartContainer(cfg RunConfig, id string) error {
202 | ctx, cancel := cfg.getContextWithTimeout()
203 | defer cancel()
204 | err := c.client.ContainerStart(ctx, id, types.ContainerStartOptions{})
205 | if err != nil {
206 | return err
207 | }
208 | return nil
209 | }
210 |
211 | func (c *DockerContainerRuntime) CleanContainer(cfg RunConfig, id string) {
212 | // cleanup procedure should use background context
213 | ctx, cancel := context.WithTimeout(context.Background(), cfg.timeout)
214 | defer cancel()
215 | // wait the container gracefully exit
216 | statusCh, errCh := c.client.ContainerWait(ctx, id, container.WaitConditionNotRunning)
217 | var rmErr error
218 | select {
219 | case err := <-errCh:
220 | if err != nil {
221 | log.Println("error waiting container exit, kill with --force")
222 | // timeout or error occurs, try force remove anywawy
223 | rmErr = c.RmContainer(cfg, id, true)
224 | }
225 | case <-statusCh:
226 | rmErr = c.RmContainer(cfg, id, false)
227 | }
228 | if rmErr != nil {
229 | log.Printf("error remove container: %s \n", id)
230 | } else if cfg.verbosity > 0 {
231 | log.Printf("Debug session end, debug container %s removed", id)
232 | }
233 | }
234 |
235 | func (c *DockerContainerRuntime) RmContainer(cfg RunConfig, id string, force bool) error {
236 | // cleanup procedure should use background context
237 | ctx, cancel := context.WithTimeout(context.Background(), cfg.timeout)
238 | defer cancel()
239 | return c.client.ContainerRemove(ctx, id,
240 | types.ContainerRemoveOptions{
241 | Force: true,
242 | })
243 | }
244 |
245 | // AttachToContainer do `docker attach`. Blocks until container I/O complete
246 | func (c *DockerContainerRuntime) AttachToContainer(cfg RunConfig, container string) error {
247 | HandleResizing(cfg.resize, func(size remotecommand.TerminalSize) {
248 | c.resizeContainerTTY(cfg, container, uint(size.Height), uint(size.Width))
249 | })
250 |
251 | opts := types.ContainerAttachOptions{
252 | Stream: true,
253 | Stdin: cfg.stdin != nil,
254 | Stdout: cfg.stdout != nil,
255 | Stderr: cfg.stderr != nil,
256 | }
257 | ctx, cancel := cfg.getContextWithTimeout()
258 | defer cancel()
259 | resp, err := c.client.ContainerAttach(ctx, container, opts)
260 | if err != nil {
261 | return err
262 | }
263 | defer resp.Close()
264 |
265 | return c.holdHijackedConnection(cfg, resp)
266 | }
267 |
268 | func (c *DockerContainerRuntime) resizeContainerTTY(cfg RunConfig, id string, height, width uint) error {
269 | ctx, cancel := cfg.getContextWithTimeout()
270 | defer cancel()
271 | return c.client.ContainerResize(ctx, id, types.ResizeOptions{
272 | Height: height,
273 | Width: width,
274 | })
275 | }
276 |
277 | // holdHijackedConnection hold the HijackedResponse, redirect the inputStream to the connection, and redirect the response
278 | // stream to stdout and stderr. NOTE: If needed, we could also add context in this function.
279 | func (c *DockerContainerRuntime) holdHijackedConnection(cfg RunConfig, resp types.HijackedResponse) error {
280 | receiveStdout := make(chan error)
281 | if cfg.stdout != nil || cfg.stderr != nil {
282 | go func() {
283 | receiveStdout <- c.redirectResponseToOutputStream(cfg, resp.Reader)
284 | }()
285 | }
286 |
287 | stdinDone := make(chan struct{})
288 | go func() {
289 | if cfg.stdin != nil {
290 | io.Copy(resp.Conn, cfg.stdin)
291 | }
292 | resp.CloseWrite()
293 | close(stdinDone)
294 | }()
295 |
296 | select {
297 | case err := <-receiveStdout:
298 | return err
299 | case <-stdinDone:
300 | if cfg.stdout != nil || cfg.stderr != nil {
301 | return <-receiveStdout
302 | }
303 | }
304 | return nil
305 | }
306 |
307 | func (c *DockerContainerRuntime) redirectResponseToOutputStream(cfg RunConfig, resp io.Reader) error {
308 | var stdout io.Writer = cfg.stdout
309 | if stdout == nil {
310 | stdout = ioutil.Discard
311 | }
312 | var stderr io.Writer = cfg.stderr
313 | if stderr == nil {
314 | stderr = ioutil.Discard
315 | }
316 | var err error
317 | if cfg.tty {
318 | _, err = io.Copy(stdout, resp)
319 | } else {
320 | _, err = stdcopy.StdCopy(stdout, stderr, resp)
321 | }
322 | return err
323 | }
324 |
325 | type ContainerdContainerRuntime struct {
326 | client *containerd.Client
327 | image containerd.Image
328 | }
329 |
330 | var ContainerdContainerRuntimeImplementsContainerRuntime ContainerRuntime = (*ContainerdContainerRuntime)(nil)
331 |
332 | var PushTracker = docker.NewInMemoryTracker()
333 |
334 | type jobs struct {
335 | name string
336 | added map[digest.Digest]struct{}
337 | descs []ocispec.Descriptor
338 | mu sync.Mutex
339 | resolved bool
340 | }
341 |
342 | func (j *jobs) isResolved() bool {
343 | j.mu.Lock()
344 | defer j.mu.Unlock()
345 | return j.resolved
346 | }
347 |
348 | func (j *jobs) jobs() []ocispec.Descriptor {
349 | j.mu.Lock()
350 | defer j.mu.Unlock()
351 |
352 | var descs []ocispec.Descriptor
353 | return append(descs, j.descs...)
354 | }
355 |
356 | func newJobs(name string) *jobs {
357 | return &jobs{
358 | name: name,
359 | added: map[digest.Digest]struct{}{},
360 | }
361 | }
362 |
363 | type StatusInfo struct {
364 | Ref string
365 | Status string
366 | Offset int64
367 | Total int64
368 | StartedAt time.Time
369 | UpdatedAt time.Time
370 | }
371 |
372 | func Display(w io.Writer, statuses []StatusInfo, start time.Time) {
373 | var total int64
374 | for _, status := range statuses {
375 | total += status.Offset
376 | switch status.Status {
377 | case "downloading", "uploading":
378 | var bar progress.Bar
379 | if status.Total > 0.0 {
380 | bar = progress.Bar(float64(status.Offset) / float64(status.Total))
381 | }
382 | fmt.Fprintf(w, "%s:\t%s\t%40r\t%8.8s/%s\t\r\n",
383 | status.Ref,
384 | status.Status,
385 | bar,
386 | progress.Bytes(status.Offset), progress.Bytes(status.Total))
387 | case "resolving", "waiting":
388 | bar := progress.Bar(0.0)
389 | fmt.Fprintf(w, "%s:\t%s\t%40r\t\r\n",
390 | status.Ref,
391 | status.Status,
392 | bar)
393 | default:
394 | bar := progress.Bar(1.0)
395 | fmt.Fprintf(w, "%s:\t%s\t%40r\t\r\n",
396 | status.Ref,
397 | status.Status,
398 | bar)
399 | }
400 | }
401 |
402 | fmt.Fprintf(w, "elapsed: %-4.1fs\ttotal: %7.6v\t(%v)\t\r\n",
403 | time.Since(start).Seconds(),
404 | // TODO(stevvooe): These calculations are actually way off.
405 | // Need to account for previously downloaded data. These
406 | // will basically be right for a download the first time
407 | // but will be skewed if restarting, as it includes the
408 | // data into the start time before.
409 | progress.Bytes(total),
410 | progress.NewBytesPerSecond(total, time.Since(start)))
411 | }
412 |
413 | func showProgress(ctx context.Context, ongoing *jobs, cs content.Store, out io.Writer) {
414 | var (
415 | ticker = time.NewTicker(100 * time.Millisecond)
416 | fw = progress.NewWriter(out)
417 | start = time.Now()
418 | statuses = map[string]StatusInfo{}
419 | done bool
420 | )
421 | defer ticker.Stop()
422 |
423 | outer:
424 | for {
425 | select {
426 | case <-ticker.C:
427 | fw.Flush()
428 |
429 | tw := tabwriter.NewWriter(fw, 1, 8, 1, ' ', 0)
430 |
431 | resolved := "resolved"
432 | if !ongoing.isResolved() {
433 | resolved = "resolving"
434 | }
435 | statuses[ongoing.name] = StatusInfo{
436 | Ref: ongoing.name,
437 | Status: resolved,
438 | }
439 | keys := []string{ongoing.name}
440 |
441 | activeSeen := map[string]struct{}{}
442 | if !done {
443 | active, err := cs.ListStatuses(ctx, "")
444 | if err != nil {
445 | glog.G(ctx).WithError(err).Error("active check failed")
446 | continue
447 | }
448 | // update status of active entries!
449 | for _, active := range active {
450 | statuses[active.Ref] = StatusInfo{
451 | Ref: active.Ref,
452 | Status: "downloading",
453 | Offset: active.Offset,
454 | Total: active.Total,
455 | StartedAt: active.StartedAt,
456 | UpdatedAt: active.UpdatedAt,
457 | }
458 | activeSeen[active.Ref] = struct{}{}
459 | }
460 | }
461 |
462 | // now, update the items in jobs that are not in active
463 | for _, j := range ongoing.jobs() {
464 | key := remotes.MakeRefKey(ctx, j)
465 | keys = append(keys, key)
466 | if _, ok := activeSeen[key]; ok {
467 | continue
468 | }
469 |
470 | status, ok := statuses[key]
471 | if !done && (!ok || status.Status == "downloading") {
472 | info, err := cs.Info(ctx, j.Digest)
473 | if err != nil {
474 | if !errdefs.IsNotFound(err) {
475 | glog.G(ctx).WithError(err).Errorf("failed to get content info")
476 | continue outer
477 | } else {
478 | statuses[key] = StatusInfo{
479 | Ref: key,
480 | Status: "waiting",
481 | }
482 | }
483 | } else if info.CreatedAt.After(start) {
484 | statuses[key] = StatusInfo{
485 | Ref: key,
486 | Status: "done",
487 | Offset: info.Size,
488 | Total: info.Size,
489 | UpdatedAt: info.CreatedAt,
490 | }
491 | } else {
492 | statuses[key] = StatusInfo{
493 | Ref: key,
494 | Status: "exists",
495 | }
496 | }
497 | } else if done {
498 | if ok {
499 | if status.Status != "done" && status.Status != "exists" {
500 | status.Status = "done"
501 | statuses[key] = status
502 | }
503 | } else {
504 | statuses[key] = StatusInfo{
505 | Ref: key,
506 | Status: "done",
507 | }
508 | }
509 | }
510 | }
511 |
512 | var ordered []StatusInfo
513 | for _, key := range keys {
514 | ordered = append(ordered, statuses[key])
515 | }
516 |
517 | Display(tw, ordered, start)
518 | tw.Flush()
519 |
520 | if done {
521 | fw.Flush()
522 | return
523 | }
524 | case <-ctx.Done():
525 | done = true // allow ui to update once more
526 | }
527 | }
528 | }
529 |
530 | func (c *ContainerdContainerRuntime) PullImage(
531 | ctx context.Context, image string, skipTLS bool,
532 | authStr string,
533 | cfg RunConfig) error {
534 |
535 | authStr, err := url.QueryUnescape(authStr)
536 | if err != nil {
537 | log.Printf("Failed to decode authStr: %v\r\n", err)
538 | return err
539 | }
540 | ctx = namespaces.WithNamespace(ctx, KubectlDebugNS)
541 |
542 | ongoing := newJobs(image)
543 | pctx, stopProgress := context.WithCancel(ctx)
544 | if cfg.verbosity > 0 {
545 | progress := make(chan struct{})
546 | go func() {
547 | if cfg.stdout != nil {
548 | // no progress bar, because it hides some debug logs
549 | showProgress(pctx, ongoing, c.client.ContentStore(), cfg.stdout)
550 | }
551 | close(progress)
552 | }()
553 | }
554 |
555 | rslvrOpts := docker.ResolverOptions{
556 | Tracker: PushTracker,
557 | }
558 |
559 | rmtOpts := []containerd.RemoteOpt{
560 | containerd.WithPullUnpack,
561 | }
562 |
563 | crds := strings.Split(authStr, ":")
564 | if cfg.verbosity > 0 {
565 | log.Printf("User name for pull : %v\r\n", crds[0])
566 | }
567 | var useCrds = len(crds) == 2
568 | if useCrds || skipTLS {
569 | tr := &http.Transport{
570 | Proxy: http.ProxyFromEnvironment,
571 | DialContext: (&net.Dialer{
572 | Timeout: 30 * time.Second,
573 | KeepAlive: 30 * time.Second,
574 | DualStack: true,
575 | }).DialContext,
576 | MaxIdleConns: 10,
577 | IdleConnTimeout: 30 * time.Second,
578 | TLSHandshakeTimeout: 10 * time.Second,
579 | TLSClientConfig: &tls.Config{
580 | InsecureSkipVerify: skipTLS,
581 | },
582 | ExpectContinueTimeout: 5 * time.Second,
583 | }
584 |
585 | rslvrOpts.Client = &http.Client{
586 | Transport: tr,
587 | }
588 |
589 | if useCrds {
590 | if cfg.verbosity > 0 {
591 | log.Println("Setting credentials call back")
592 | }
593 | crdsClbck := func(host string) (string, string, error) {
594 | if cfg.verbosity > 0 {
595 | log.Printf("crdsClbck returning username: %v\r\n", crds[0])
596 | }
597 | return crds[0], crds[1], nil
598 | }
599 |
600 | authOpts := []docker.AuthorizerOpt{
601 | docker.WithAuthClient(rslvrOpts.Client), docker.WithAuthCreds(crdsClbck),
602 | }
603 |
604 | rslvrOpts.Authorizer = docker.NewDockerAuthorizer(authOpts...)
605 | }
606 | rmtOpts = append(rmtOpts, containerd.WithResolver(docker.NewResolver(rslvrOpts)))
607 | }
608 |
609 | c.image, err = c.client.Pull(ctx, image, rmtOpts...)
610 | stopProgress()
611 |
612 | if err != nil {
613 | log.Printf("Failed to download image: %v\r\n", err)
614 | return err
615 | }
616 | return err
617 | }
618 |
619 | func (c *ContainerdContainerRuntime) ContainerInfo(
620 | ctx context.Context, cfg RunConfig) (ContainerInfo, error) {
621 | var ret ContainerInfo
622 | ctx = namespaces.WithNamespace(ctx, K8NS)
623 | cntnr, err := c.client.LoadContainer(ctx, cfg.idOfContainerToDebug)
624 | if err != nil {
625 | log.Printf("Failed to access target container %s : %v\r\n",
626 | cfg.idOfContainerToDebug, err)
627 |
628 | return ContainerInfo{}, err
629 | }
630 | tsk, err := cntnr.Task(ctx, nil)
631 | if err != nil {
632 | log.Printf("Failed to get task of target container %s : %v\r\n",
633 | cfg.idOfContainerToDebug, err)
634 |
635 | return ContainerInfo{}, err
636 | }
637 | pids, err := tsk.Pids(ctx)
638 | if err != nil {
639 | log.Printf("Failed to get pids of target container %s : %v\r\n",
640 | cfg.idOfContainerToDebug, err)
641 |
642 | return ContainerInfo{}, err
643 | }
644 |
645 | info, err := cntnr.Info(ctx, containerd.WithoutRefreshedMetadata)
646 | if err != nil {
647 | log.Printf("Failed to load target container info %s : %v\r\n",
648 | cfg.idOfContainerToDebug, err)
649 |
650 | return ContainerInfo{}, err
651 | }
652 |
653 | if cfg.verbosity > 0 {
654 | log.Printf("Pids from target container: %+v\r\n", pids)
655 | }
656 | ret.Pid = int64(pids[0].Pid)
657 | if info.Spec != nil && info.Spec.Value != nil {
658 | v, err := typeurl.UnmarshalAny(info.Spec)
659 | if err != nil {
660 | log.Printf("Error unmarshalling spec for container %s : %v\r\n",
661 | cfg.idOfContainerToDebug, err)
662 | }
663 | for _, mnt := range v.(*specs.Spec).Mounts {
664 | ret.MountDestinations = append(
665 | ret.MountDestinations, mnt.Destination)
666 | fmt.Printf("%+v\r\n", mnt)
667 | }
668 | }
669 | return ret, nil
670 | }
671 |
672 | const (
673 | // netNSFormat is the format of network namespace of a process.
674 | netNSFormat = "/proc/%v/ns/net"
675 | // ipcNSFormat is the format of ipc namespace of a process.
676 | ipcNSFormat = "/proc/%v/ns/ipc"
677 | // utsNSFormat is the format of uts namespace of a process.
678 | userNSFormat = "/proc/%v/ns/user"
679 | // pidNSFormat is the format of pid namespace of a process.
680 | pidNSFormat = "/proc/%v/ns/pid"
681 | )
682 |
683 | func GetNetworkNamespace(pid int64) string {
684 | return fmt.Sprintf(netNSFormat, pid)
685 | }
686 | func GetIPCNamespace(pid int64) string {
687 | return fmt.Sprintf(ipcNSFormat, pid)
688 | }
689 | func GetUserNamespace(pid int64) string {
690 | return fmt.Sprintf(userNSFormat, pid)
691 | }
692 | func GetPIDNamespace(pid int64) string {
693 | return fmt.Sprintf(pidNSFormat, pid)
694 | }
695 |
696 | func (c *ContainerdContainerRuntime) RunDebugContainer(cfg RunConfig) error {
697 | defer c.client.Close()
698 |
699 | uuid := uuid.New().String()
700 | fifoNm := ""
701 | if cfg.audit {
702 | fifoDir, _ := path.Split(cfg.auditFifo)
703 | err := os.MkdirAll(fifoDir, 0777)
704 | if err != nil {
705 | fmt.Printf("Failed to create directory for audit fifos, %v : %v\r\n", fifoDir, err)
706 | return err
707 | }
708 | fifoNm = strings.ReplaceAll(cfg.auditFifo, "KCTLDBG-CONTAINER-ID", uuid)
709 | if cfg.verbosity > 0 {
710 | log.Printf("Creating fifo %v for receiving audit data.\r\n", fifoNm)
711 | }
712 | err = syscall.Mkfifo(fifoNm, 0600)
713 | if err != nil {
714 | fmt.Printf("Failed to create audit fifo %v : %v\r\n", fifoNm, err)
715 | return err
716 | } else {
717 | defer os.Remove(fifoNm)
718 | }
719 |
720 | go func() {
721 | log.Println("Audit read thread started.")
722 | fl, rdErr := os.Open(fifoNm)
723 | if rdErr != nil {
724 | log.Printf("Audit read thread aborting. Failed to open fifo : %v\r\n", rdErr)
725 | return
726 | }
727 | log.Println("Audit read thread started.")
728 | defer fl.Close()
729 | rdr := bufio.NewReader(fl)
730 | var ln []byte
731 | for {
732 | ln, _, rdErr = rdr.ReadLine()
733 | if rdErr != nil {
734 | break
735 | }
736 | log.Printf("audit - user: %v debugee: %v exec: %v\r\n", cfg.clientUserName,
737 | cfg.idOfContainerToDebug, string(ln))
738 | }
739 | if rdErr != nil {
740 | if rdErr == io.EOF {
741 | log.Printf("EOF reached while reading from %v. Audit read thread exiting.\r\n", fifoNm)
742 | } else {
743 | log.Printf("Error %v while reading from %v. Audit read thread exiting.\r\n", rdErr, fifoNm)
744 | }
745 | }
746 | }()
747 | }
748 | // If audit, create thread for reading from fifo, defer clean up of thread
749 | ctx := namespaces.WithNamespace(cfg.context, KubectlDebugNS)
750 |
751 | var spcOpts []oci.SpecOpts
752 | spcOpts = append(spcOpts, oci.WithImageConfig(c.image))
753 | spcOpts = append(spcOpts, oci.WithPrivileged)
754 | // if audit, build command vector array using shim + cfg.command
755 | // Make sure to replace KCTLDBG-FIFO with the actual fifo path ( Or maybe that is done before we get this far )
756 | if cfg.audit {
757 | cmd := append([]string(nil), cfg.auditShim...)
758 | for i, s := range cmd {
759 | cmd[i] = strings.ReplaceAll(s, "KCTLDBG-FIFO", fifoNm)
760 | }
761 | cmd = append(cmd, cfg.command...)
762 | spcOpts = append(spcOpts, oci.WithProcessArgs(cmd...))
763 | } else {
764 | spcOpts = append(spcOpts, oci.WithProcessArgs(cfg.command...))
765 | }
766 | spcOpts = append(spcOpts, oci.WithTTY)
767 | // If fifo, make sure fifo is bind mounted
768 | trgtInf, err := c.ContainerInfo(ctx, cfg)
769 | if err != nil {
770 | log.Printf("Failed to get a pid from target container %s : %v\r\n",
771 | cfg.idOfContainerToDebug, err)
772 | return err
773 | }
774 | spcOpts = append(spcOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{
775 | Type: specs.NetworkNamespace,
776 | Path: GetNetworkNamespace(trgtInf.Pid),
777 | }))
778 |
779 | if fifoNm != "" {
780 | kbctlDbgMnt := specs.Mount{
781 | Destination: fifoNm,
782 | Source: fifoNm,
783 | Type: "bind",
784 | Options: []string{"bind", "rw"},
785 | }
786 | spcOpts = append(spcOpts, oci.WithMounts([]specs.Mount{kbctlDbgMnt}))
787 | }
788 | // 2020-04-21 d :
789 | // Tried setting the user namespace without success.
790 | // - If I just use WithLinuxNamespace and don't use WithUserNamespace
791 | // then I get the error "User namespaces enabled, but no uid mappings found.: unknown"
792 | // - If I then add WithUserNamespace I instead get the error
793 | // "getting the final child's pid from pipe caused \"EOF\"": unknown"
794 | //
795 | // I examined one of our environments, checked available kubernetes settings it seems
796 | // really all containers are sharing the host user namespace. I then stumbled on this
797 | // https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/
798 | // article which claims that Kubernetes doesn't provide a way to set up separate user
799 | // namespaces for containers.
800 | // Consequently am just going to comment this out for now.
801 | // spcOpts = append(spcOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{
802 | // Type: specs.UserNamespace,
803 | // Path: GetUserNamespace(trgtInf.Pid),
804 | // }))
805 | // spcOpts = append(spcOpts, oci.WithUserNamespace(0, 0, 1024))
806 | spcOpts = append(spcOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{
807 | Type: specs.IPCNamespace,
808 | Path: GetIPCNamespace(trgtInf.Pid),
809 | }))
810 | spcOpts = append(spcOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{
811 | Type: specs.PIDNamespace,
812 | Path: GetPIDNamespace(trgtInf.Pid),
813 | }))
814 | cntnr, err := c.client.NewContainer(
815 | ctx,
816 | // Was using "dbg-[idOfContainerToDebug]" but this meant that you couldn't use multiple debug containers for the same debugee
817 | // e.g. You couldn't have 1 running tcpdump and another one generating traffic.
818 | uuid,
819 | containerd.WithImage(c.image),
820 | containerd.WithNewSnapshot("netshoot-snapshot-"+uuid, c.image), // Had hoped this would fix 2020/04/17 17:04:31 runtime.go:672: Failed to create container for debugging 3d4059893a086fc7c59991fde9835ac7e35b754cd017a300292af9c721a4e6b9 : rootfs absolute path is required but it did not
821 | containerd.WithNewSpec(spcOpts...),
822 | )
823 |
824 | if cntnr != nil {
825 | // Label the container so we have some idea of who created it and why
826 | lbls := make(map[string]string)
827 | lbls["ClientHostName"] = cfg.clientHostName
828 | lbls["ClientUserName"] = cfg.clientUserName
829 | lbls["IdOfDebuggee"] = cfg.idOfContainerToDebug
830 | cntnr.SetLabels(ctx, lbls)
831 |
832 | defer func() {
833 | cdctx, ccancel := context.WithTimeout(context.Background(), cfg.timeout)
834 | defer ccancel()
835 | cdctx = namespaces.WithNamespace(cdctx, KubectlDebugNS)
836 | cderr := cntnr.Delete(cdctx, containerd.WithSnapshotCleanup)
837 | if cderr != nil {
838 | log.Printf("Failed to delete container for debugging %s : %v\r\n",
839 | cfg.idOfContainerToDebug, cderr)
840 | }
841 | }()
842 | }
843 |
844 | if err != nil {
845 | log.Printf("Failed to create container for debugging %s : %v\r\n",
846 | cfg.idOfContainerToDebug, err)
847 | return err
848 | }
849 |
850 | var stdIo cio.Opt
851 | if cfg.stderr == nil {
852 | // 2020-04-21 d : Otherwise create fails with
853 | // E0421 14:16:36.797876 24356 attach.go:54] error attaching to container: failed to start io pipe copy: unable to copy pipes: containerd-shim: opening file "" failed: open : no such file or directory: unknown
854 | stdIo = cio.WithStreams(cfg.stdin, cfg.stdout, cfg.stdout)
855 | } else {
856 | stdIo = cio.WithStreams(cfg.stdin, cfg.stdout, cfg.stderr)
857 | }
858 |
859 | tsk, err := cntnr.NewTask(ctx,
860 | cio.NewCreator(
861 | stdIo,
862 | cio.WithTerminal,
863 | ))
864 |
865 | if tsk != nil {
866 | defer func() {
867 | tdctx, tcancel := context.WithTimeout(context.Background(), cfg.timeout)
868 | defer tcancel()
869 | tdctx = namespaces.WithNamespace(tdctx, KubectlDebugNS)
870 | _, tderr := tsk.Delete(tdctx, containerd.WithProcessKill)
871 | if tderr != nil {
872 | log.Printf("Failed to delete task for debugging %s : %v\r\n",
873 | cfg.idOfContainerToDebug, tderr)
874 | }
875 | }()
876 | }
877 |
878 | if err != nil {
879 | log.Printf("Failed to create task for debugging %s : %v\r\n",
880 | cfg.idOfContainerToDebug, err)
881 | return err
882 | }
883 |
884 | exitStatusC, err := tsk.Wait(ctx)
885 | if err != nil {
886 | log.Printf("Failed to get exit channel for task for debugging %s : %v\r\n",
887 | cfg.idOfContainerToDebug, err)
888 | return err
889 | }
890 |
891 | HandleResizing(cfg.resize, func(size remotecommand.TerminalSize) {
892 | c.resizeContainerTTY(ctx, cfg.idOfContainerToDebug, tsk, size.Height,
893 | size.Width)
894 | })
895 |
896 | if err := tsk.Start(ctx); err != nil {
897 | return err
898 | }
899 |
900 | status := <-exitStatusC
901 | _, _, err = status.Result()
902 | if err != nil {
903 | log.Printf("Failed to get exit status for task for debugging %s : %v\r\n",
904 | cfg.idOfContainerToDebug, err)
905 | return err
906 | }
907 |
908 | return nil
909 | }
910 |
911 | func (c *ContainerdContainerRuntime) resizeContainerTTY(ctx context.Context,
912 | trgtId string, tsk containerd.Task, height, width uint16) error {
913 | err := tsk.Resize(ctx, uint32(width), uint32(height))
914 | if err != nil {
915 | log.Printf("Failed to resize debugger task %+v for debuggee %+v : %+v\r\n",
916 | tsk.Pid(), trgtId, err)
917 | }
918 | return nil
919 | }
920 |
921 | // DebugAttacher implements Attacher
922 | // we use this struct in order to inject debug info (image, command) in the debug procedure
923 | type DebugAttacher struct {
924 | containerRuntime ContainerRuntime
925 | image string
926 | authStr string
927 | registrySkipTLS bool
928 | lxcfsEnabled bool
929 | command []string
930 | timeout time.Duration
931 | idOfContainerToDebug string
932 | verbosity int
933 | clientHostName string
934 | clientUserName string
935 |
936 | // control the preparing of debug container
937 | stopListenEOF chan struct{}
938 | context context.Context
939 | cancel context.CancelFunc
940 |
941 | // audit options
942 | audit bool
943 | auditFifo string
944 | auditShim []string
945 | }
946 |
947 | var DebugAttacherImplementsAttacher kubeletremote.Attacher = (*DebugAttacher)(nil)
948 |
949 | // Implement kubeletremote.Attacher
950 | func (a *DebugAttacher) AttachContainer(name string, uid kubetype.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
951 | if a.verbosity > 0 {
952 | log.Println("Enter")
953 |
954 | if resize == nil {
955 | log.Println("Resize channel is nil")
956 | }
957 | }
958 |
959 | return a.DebugContainer(RunConfig{
960 | context: a.context,
961 | timeout: a.timeout,
962 | idOfContainerToDebug: a.idOfContainerToDebug,
963 | image: a.image,
964 | command: a.command,
965 | stdin: in,
966 | stdout: out,
967 | stderr: err,
968 | tty: tty,
969 | resize: resize,
970 | clientHostName: a.clientHostName,
971 | clientUserName: a.clientUserName,
972 | verbosity: a.verbosity,
973 | audit: a.audit,
974 | auditFifo: a.auditFifo,
975 | auditShim: a.auditShim,
976 | })
977 | }
978 |
979 | // DebugContainer executes the main debug flow
980 | func (m *DebugAttacher) DebugContainer(cfg RunConfig) error {
981 |
982 | if m.verbosity > 0 {
983 | log.Printf("Accept new debug request:\n\t target container: %s \n\t image: %s \n\t command: %v \n\r", m.idOfContainerToDebug, m.image, m.command)
984 | }
985 |
986 | // the following steps may takes much time,
987 | // so we listen to EOF from stdin
988 | // which helps user to terminate the procedure proactively
989 |
990 | // FIXME: the following logic will 'eat' a character
991 | //var buf bytes.Buffer
992 | //tee := io.TeeReader(stdin, &buf)
993 | //go func() {
994 | // p := make([]byte, 4)
995 | // OUTER:
996 | // for {
997 | // select {
998 | // case <- m.stopListenEOF:
999 | // break OUTER
1000 | // default:
1001 | // n, err := tee.Read(p)
1002 | // // 4 -> EOT
1003 | // if (n > 0 && binary.LittleEndian.Uint32(p) == 4) || err == io.EOF {
1004 | // log.Println("receive ctrl-d or EOF when preparing debug container, cancel session")
1005 | // m.cancel()
1006 | // break OUTER
1007 | // }
1008 | // }
1009 | // }
1010 | //} ()
1011 | // step 0: set container procfs to lxcfs
1012 | if cfg.verbosity > 0 {
1013 | cfg.stdout.Write([]byte(fmt.Sprintf("set container procfs lxcfs: %t .. \n\r", m.lxcfsEnabled)))
1014 | }
1015 | if m.lxcfsEnabled {
1016 | if err := CheckLxcfsMount(); err != nil {
1017 | return err
1018 | }
1019 |
1020 | if err := m.SetContainerLxcfs(cfg); err != nil {
1021 | return err
1022 | }
1023 | }
1024 |
1025 | // step 1: pull image
1026 | if cfg.verbosity > 0 {
1027 | cfg.stdout.Write([]byte(fmt.Sprintf("pulling image %s, skip TLS %v... \n\r", m.image, m.registrySkipTLS)))
1028 | }
1029 | err := m.containerRuntime.PullImage(m.context, m.image, m.registrySkipTLS, m.authStr, cfg)
1030 | if err != nil {
1031 | cfg.stdout.Write([]byte(fmt.Sprintf("pulling image %s, \n\r", m.image)))
1032 | return err
1033 | }
1034 |
1035 | // step 2: run debug container (join the namespaces of target container)
1036 | if cfg.verbosity > 0 {
1037 | cfg.stdout.Write([]byte("starting debug container...\n\r"))
1038 | }
1039 | return m.containerRuntime.RunDebugContainer(cfg)
1040 | }
1041 |
1042 | func (m *DebugAttacher) SetContainerLxcfs(cfg RunConfig) error {
1043 | ctx, cancel := cfg.getContextWithTimeout()
1044 | defer cancel()
1045 | cntnrInf, err := m.containerRuntime.ContainerInfo(ctx, cfg)
1046 | if err != nil {
1047 | return err
1048 | }
1049 | for _, mntDst := range cntnrInf.MountDestinations {
1050 | if mntDst == LxcfsRootDir {
1051 | if cfg.verbosity > 0 {
1052 | log.Printf("remount lxcfs when the rootdir of lxcfs of target container has been mounted. \n\t ")
1053 | }
1054 | for _, procfile := range LxcfsProcFiles {
1055 | nsenter := &nsenter.MountNSEnter{
1056 | Target: cntnrInf.Pid,
1057 | MountLxcfs: true,
1058 | }
1059 | _, stderr, err := nsenter.Execute("--", "mount", "-B", LxcfsHomeDir+procfile, procfile)
1060 | if err != nil {
1061 | log.Printf("bind mount lxcfs files failed. \n\t reason: %s", stderr)
1062 | return err
1063 | }
1064 | }
1065 | }
1066 | }
1067 |
1068 | return nil
1069 | }
1070 |
1071 | // RuntimeManager is responsible for docker operation
1072 | type RuntimeManager struct {
1073 | dockerClient *dockerclient.Client
1074 | containerdClient *containerd.Client
1075 | timeout time.Duration
1076 | verbosity int
1077 | idOfContainerToDebug string
1078 | containerScheme ContainerRuntimeScheme
1079 | clientHostName string
1080 | clientUserName string
1081 | audit bool
1082 | auditFifo string
1083 | auditShim []string
1084 | }
1085 |
1086 | func NewRuntimeManager(srvCfg Config, containerUri string, verbosity int,
1087 | hstNm, usrNm string) (*RuntimeManager, error) {
1088 | if len(containerUri) < 1 {
1089 | return nil, errors.New("target container id must be provided")
1090 | }
1091 | containerUriParts := strings.SplitN(containerUri, "://", 2)
1092 | if len(containerUriParts) != 2 {
1093 | msg := fmt.Sprintf("target container id must have form scheme:id but was %v", containerUri)
1094 | if verbosity > 0 {
1095 | log.Println(msg)
1096 | }
1097 | return nil, errors.New(msg)
1098 | }
1099 | containerScheme := ContainerRuntimeScheme(containerUriParts[0])
1100 | idOfContainerToDebug := containerUriParts[1]
1101 |
1102 | var dockerClient *dockerclient.Client
1103 | var containerdClient *containerd.Client
1104 | switch containerScheme {
1105 | case DockerScheme:
1106 | {
1107 | var err error
1108 | dockerClient, err = dockerclient.NewClient(srvCfg.DockerEndpoint, "", nil, nil)
1109 | if err != nil {
1110 | return nil, err
1111 | }
1112 | }
1113 | case ContainerdScheme:
1114 | {
1115 | var err error
1116 | var clntOpts []containerd.ClientOpt
1117 | if os.Getenv("KCTLDBG_CONTAINERDV1_SHIM") != "" {
1118 | if verbosity > 0 {
1119 | log.Println("Using containerd v1 runtime")
1120 | }
1121 | clntOpts = append(clntOpts,
1122 | containerd.WithDefaultRuntime("io.containerd.runc.v1"))
1123 | }
1124 | containerdClient, err = containerd.New(srvCfg.ContainerdEndpoint,
1125 | clntOpts...)
1126 | if err != nil {
1127 | return nil, err
1128 | }
1129 | }
1130 | default:
1131 | {
1132 | msg := "only docker and containerd container runtimes are suppored right now"
1133 | log.Println(msg)
1134 | return nil, errors.New(msg)
1135 | }
1136 | }
1137 |
1138 | return &RuntimeManager{
1139 | dockerClient: dockerClient,
1140 | containerdClient: containerdClient,
1141 | timeout: srvCfg.RuntimeTimeout,
1142 | verbosity: verbosity,
1143 | idOfContainerToDebug: idOfContainerToDebug,
1144 | containerScheme: containerScheme,
1145 | clientHostName: hstNm,
1146 | clientUserName: usrNm,
1147 | audit: srvCfg.Audit,
1148 | auditFifo: srvCfg.AuditFifo,
1149 | auditShim: srvCfg.AuditShim,
1150 | }, nil
1151 | }
1152 |
1153 | // GetAttacher returns an implementation of Attacher
1154 | func (m *RuntimeManager) GetAttacher(image, authStr string,
1155 | lxcfsEnabled, registrySkipTLS bool,
1156 | command []string, context context.Context,
1157 | cancel context.CancelFunc) kubeletremote.Attacher {
1158 | var containerRuntime ContainerRuntime
1159 | if m.dockerClient != nil {
1160 | containerRuntime = &DockerContainerRuntime{
1161 | client: m.dockerClient,
1162 | }
1163 | } else {
1164 | containerRuntime = &ContainerdContainerRuntime{
1165 | client: m.containerdClient,
1166 | }
1167 | }
1168 | return &DebugAttacher{
1169 | containerRuntime: containerRuntime,
1170 | image: image,
1171 | authStr: authStr,
1172 | lxcfsEnabled: lxcfsEnabled,
1173 | registrySkipTLS: registrySkipTLS,
1174 | command: command,
1175 | context: context,
1176 | idOfContainerToDebug: m.idOfContainerToDebug,
1177 | verbosity: m.verbosity,
1178 | timeout: m.timeout,
1179 | cancel: cancel,
1180 | stopListenEOF: make(chan struct{}),
1181 | clientHostName: m.clientHostName,
1182 | clientUserName: m.clientUserName,
1183 | audit: m.audit,
1184 | auditFifo: m.auditFifo,
1185 | auditShim: m.auditShim,
1186 | }
1187 | }
1188 |
--------------------------------------------------------------------------------
/pkg/debug-agent/server.go:
--------------------------------------------------------------------------------
1 | package debugagent
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "os"
10 | "os/signal"
11 | "strconv"
12 | "strings"
13 | "time"
14 |
15 | remoteapi "k8s.io/apimachinery/pkg/util/remotecommand"
16 | kubeletremote "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
17 | )
18 |
19 | type Server struct {
20 | config *Config
21 | }
22 |
23 | func NewServer(config *Config) (*Server, error) {
24 | return &Server{config: config}, nil
25 | }
26 |
27 | func (s *Server) Run() error {
28 |
29 | stop := make(chan os.Signal, 1)
30 | signal.Notify(stop, os.Interrupt)
31 |
32 | mux := http.NewServeMux()
33 | mux.HandleFunc("/api/v1/debug", s.ServeDebug)
34 | mux.HandleFunc("/healthz", s.Healthz)
35 | server := &http.Server{Addr: s.config.ListenAddress, Handler: mux}
36 |
37 | go func() {
38 | log.Printf("Listening on %s \n", s.config.ListenAddress)
39 |
40 | if err := server.ListenAndServe(); err != nil {
41 | log.Fatal(err)
42 | }
43 | }()
44 | <-stop
45 |
46 | log.Println("shutting down server...")
47 |
48 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
49 | defer cancel()
50 | server.Shutdown(ctx)
51 |
52 | return nil
53 | }
54 |
55 | func maxInt(lhs, rhs int) int {
56 | if lhs >= rhs {
57 | return lhs
58 | }
59 | return rhs
60 | }
61 |
62 | // ServeDebug serves the debug request.
63 | // first, it will upgrade the connection to SPDY.
64 | // then, server will try to create the debug container, and sent creating progress to user via SPDY.
65 | // after the debug container running, server attach to the debug container and pipe the streams to user.
66 | // once connection closed, server killed the debug container and release related resources
67 | // if any error occurs above, an error status were written to the user's stderr.
68 | func (s *Server) ServeDebug(w http.ResponseWriter, req *http.Request) {
69 |
70 | log.Println("received debug request")
71 | containerUri := req.FormValue("container")
72 |
73 | sverbosity := req.FormValue("verbosity")
74 | if sverbosity == "" {
75 | sverbosity = "0"
76 | }
77 | iverbosity, _ := strconv.Atoi(sverbosity)
78 |
79 | imageFromPlugin := req.FormValue("image")
80 | imageFromEnv := os.Getenv("KCTLDBG_RESTRICT_IMAGE_TO")
81 | var image string
82 | if len(imageFromEnv) > 0 {
83 | image = imageFromEnv
84 | if imageFromPlugin != imageFromEnv && iverbosity > 0 {
85 | log.Printf("Using image %v, specified by env var KCTLDBG_RESTRICT_IMAGE_TO on agent, instead of image %v specified by client.\r\n",
86 | imageFromEnv, imageFromPlugin)
87 | }
88 | } else {
89 | image = imageFromPlugin
90 | }
91 | if len(image) < 1 {
92 | http.Error(w, "image must be provided", 400)
93 | return
94 | }
95 | command := req.FormValue("command")
96 | var commandSlice []string
97 | err := json.Unmarshal([]byte(command), &commandSlice)
98 | if err != nil || len(commandSlice) < 1 {
99 | http.Error(w, "cannot parse command", 400)
100 | return
101 | }
102 | authStr := req.FormValue("authStr")
103 | streamOpts := &kubeletremote.Options{
104 | Stdin: true,
105 | Stdout: true,
106 | Stderr: false,
107 | TTY: true,
108 | }
109 | lxcfsEnabled := req.FormValue("lxcfsEnabled")
110 | if lxcfsEnabled == "" || lxcfsEnabled == "false" {
111 | LxcfsEnabled = false
112 | } else if lxcfsEnabled == "true" {
113 | LxcfsEnabled = true
114 | }
115 | var registrySkipTLS bool
116 | registrySkipTLSParam := req.FormValue("registrySkipTLS")
117 | if registrySkipTLSParam == "" || registrySkipTLSParam == "false" {
118 | registrySkipTLS = false
119 | } else if registrySkipTLSParam == "true" {
120 | registrySkipTLS = true
121 | }
122 |
123 | context, cancel := context.WithCancel(req.Context())
124 | defer cancel()
125 |
126 | runtime, err := NewRuntimeManager(*s.config, containerUri,
127 | maxInt(iverbosity, s.config.Verbosity),
128 | req.FormValue("hostname"),
129 | req.FormValue("username"))
130 | if err != nil {
131 | msg := fmt.Sprintf("Failed to construct RuntimeManager. Error: %s", err.Error())
132 | log.Println(msg)
133 | // 2020-04-15 d :
134 | // The client will be in SPDY roundtripper when we return this. This passes the response to
135 | // statusCodecs.UniversalDecoder().Decode. Decode will see any ":" as indication that the
136 | // response bytes are an object to be deserialized and consequently our message to the client
137 | // will be lost.
138 | http.Error(w, strings.ReplaceAll(msg, ":", "-"), 400)
139 | return
140 | }
141 |
142 | // replace Attacher implementation to hook the ServeAttach procedure
143 | if s.config.Verbosity > 0 {
144 | log.Println("Invoking kubeletremote.ServeAttach")
145 | }
146 |
147 | kubeletremote.ServeAttach(
148 | w,
149 | req,
150 | runtime.GetAttacher(image, authStr, LxcfsEnabled, registrySkipTLS,
151 | commandSlice, context, cancel),
152 | "",
153 | "",
154 | "",
155 | streamOpts,
156 | s.config.StreamIdleTimeout,
157 | s.config.StreamCreationTimeout,
158 | remoteapi.SupportedStreamingProtocols)
159 | if s.config.Verbosity > 0 {
160 | log.Println("kubeletremote.ServeAttach returned")
161 | }
162 | }
163 |
164 | func (s *Server) Healthz(w http.ResponseWriter, req *http.Request) {
165 | w.Write([]byte("I am OK"))
166 | }
167 |
--------------------------------------------------------------------------------
/pkg/kubectl-debug/cmd.go:
--------------------------------------------------------------------------------
1 | package kubectldebug
2 |
3 | import (
4 | "context"
5 | "encoding/base64"
6 | "encoding/json"
7 | "fmt"
8 | "io"
9 | "log"
10 | "net/http"
11 | "net/url"
12 | "os"
13 | "path/filepath"
14 | "strconv"
15 | "sync"
16 | "time"
17 |
18 | dockerterm "github.com/docker/docker/pkg/term"
19 | "github.com/rs/xid"
20 | "github.com/spf13/cobra"
21 | authorizationv1 "k8s.io/api/authorization/v1"
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/apimachinery/pkg/api/errors"
24 | "k8s.io/apimachinery/pkg/api/resource"
25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | "k8s.io/apimachinery/pkg/labels"
27 | "k8s.io/apimachinery/pkg/util/intstr"
28 | "k8s.io/apimachinery/pkg/util/uuid"
29 | "k8s.io/cli-runtime/pkg/genericclioptions"
30 | "k8s.io/client-go/kubernetes"
31 | coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
32 | restclient "k8s.io/client-go/rest"
33 | cmdapi "k8s.io/client-go/tools/clientcmd/api"
34 | "k8s.io/client-go/tools/portforward"
35 | "k8s.io/client-go/tools/remotecommand"
36 | "k8s.io/client-go/tools/watch"
37 | "k8s.io/client-go/transport/spdy"
38 | "k8s.io/kubernetes/pkg/client/conditions"
39 | cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
40 | "k8s.io/kubernetes/pkg/util/interrupt"
41 |
42 | term "github.com/jamestgrant/kubectl-debug/pkg/util"
43 | "github.com/jamestgrant/kubectl-debug/version"
44 | )
45 |
46 | const (
47 | example = `
48 | # print the help
49 | kubectl-debug -h
50 |
51 | # start the debug container in the same namespace, and cgroup etc as container 'CONTAINER_NAME'
52 | # in pod 'POD_NAME' in namespace 'NAMESPACE'
53 | kubectl-debug --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME
54 |
55 | # in case of your pod stuck in CrashLoopBackoff state and cannot be connected to,
56 | # you can fork a new pod and diagnose the problem in the forked pod
57 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --fork
58 |
59 | # In 'fork' mode, if you want the copied pod to retain the labels of the original pod, you can
60 | # use the --fork-pod-retain-labels parameter (comma separated, no spaces). If not set (default),
61 | # this parameter is empty and so any labels of the original pod are not retained, and the labels
62 | # of the copied pods are empty.
63 | # Example of fork mode:
64 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --fork --fork-pod-retain-labels=,,
65 |
66 | # in order to interact with the debug-agent pod on a node which doesn't have a public IP or direct
67 | # access (firewall and other reasons) to access, port-forward mode is enabled by default. if you don't
68 | # want port-forward mode, you can use --port-forward false to turn off it. I don't know why you'd want
69 | # to do this, but you can if you want.
70 | kubectl-debug --port-forward=false --namespace NAMESPACE POD_NAME -c CONTAINER_NAME
71 |
72 | # you can choose a different debug container image. By default, nicolaka/netshoot:latest will be used
73 | # but you can specify anything you like
74 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --image nicolaka/netshoot:latest
75 |
76 | # you can set the debug-agent pod's resource limits/requests, for example:
77 | # default is not set
78 | kubectl-debug --namespace NAMESPACE POD_NAME -c CONTAINER_NAME --agent-pod-cpu-requests=250m --agent-pod-cpu-limits=500m --agent-pod-memory-requests=200Mi --agent-pod-memory-limits=500Mi
79 |
80 | # use primary docker registry, set registry kubernetes secret to pull image
81 | # the default registry-secret-name is kubectl-debug-registry-secret, the default namespace is default
82 | # please set the secret data source as {Username: , Password: }
83 | kubectl-debug --namespace NAMESPACE POD_NAME --image nicolaka/netshoot:latest --registry-secret-name --registry-secret-namespace
84 | `
85 | longDesc = `
86 | kubectl-debug is an 'out-of-tree' solution for connecting to and troubleshooting an existing,
87 | running, 'target' container in an existing pod in a Kubernetes cluster.
88 | The target container may have a shell and busybox utils and hence provide some debug capability or it
89 | may be very minimal and not even provide a shell - which makes any real-time troubleshooting/debugging
90 | very difficult. kubectl-debug is designed to overcome that difficulty.
91 | `
92 | usageError = "run like this: kubectl-debug --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME"
93 | defaultDebugContainerImage = "docker.io/nicolaka/netshoot:latest"
94 |
95 | defaultDebugAgentPort = 10027
96 | defaultDebugAgentConfigFileLocation = "/tmp/debugAgentConfigFile"
97 | defaultDebugAgentImage = "jamesgrantmediakind/debug-agent:latest"
98 | defaultDebugAgentImagePullPolicy = string(corev1.PullIfNotPresent)
99 | defaultDebugAgentImagePullSecretName = ""
100 | defaultDebugAgentPodNamePrefix = "debug-agent-pod"
101 | defaultDebugAgentPodNamespace = "default"
102 | defaultDebugAgentPodCpuRequests = ""
103 | defaultDebugAgentPodCpuLimits = ""
104 | defaultDebugAgentPodMemoryRequests = ""
105 | defaultDebugAgentPodMemoryLimits = ""
106 | defaultDebugAgentDaemonSetName = "debug-agent"
107 |
108 | defaultRegistrySecretName = "kubectl-debug-registry-secret"
109 | defaultRegistrySecretNamespace = "default"
110 | defaultRegistrySkipTLSVerify = false
111 | defaultPortForward = true
112 | defaultCreateDebugAgentPod = true
113 | defaultLxcfsEnable = true
114 | defaultVerbosity = 0
115 | )
116 |
117 | // DebugOptions specify how to run debug container in a running pod
118 | type DebugOptions struct {
119 |
120 | // target pod select options
121 | Namespace string
122 | PodName string
123 | Fork bool
124 | ForkPodRetainLabels []string
125 |
126 | // Debug-container options
127 | Image string
128 | RegistrySecretName string
129 | RegistrySecretNamespace string
130 | RegistrySkipTLSVerify bool
131 | IsLxcfsEnabled bool
132 | ContainerName string
133 | Command []string
134 | AppName string
135 | ConfigLocation string
136 |
137 | // Debug-agent options
138 | CreateDebugAgentPod bool
139 | AgentImage string
140 | AgentPort int
141 | AgentImagePullPolicy string
142 | AgentImagePullSecretName string
143 |
144 | // agentPodName = agentPodNamePrefix + nodeName
145 | AgentPodName string
146 | AgentPodNamespace string
147 | AgentPodNode string
148 | AgentPodResource agentPodResources
149 |
150 | Flags *genericclioptions.ConfigFlags
151 | CoreClient coreclient.CoreV1Interface
152 | KubeCli *kubernetes.Clientset
153 | Args []string
154 | Config *restclient.Config
155 |
156 | // use for port-forward
157 | RESTClient *restclient.RESTClient
158 | PortForwarder portForwarder
159 | Ports []string
160 | StopChannel chan struct{}
161 | ReadyChannel chan struct{}
162 |
163 | PortForward bool
164 | DebugAgentDaemonSet string
165 | DebugAgentNamespace string
166 |
167 | genericclioptions.IOStreams
168 |
169 | wait sync.WaitGroup
170 |
171 | Verbosity int
172 | Logger *log.Logger
173 | UserName string
174 | }
175 |
176 | type agentPodResources struct {
177 | CpuRequests string
178 | CpuLimits string
179 | MemoryRequests string
180 | MemoryLimits string
181 | }
182 |
183 | // NewDebugOptions new debug options
184 | func NewDebugOptions(streams genericclioptions.IOStreams) *DebugOptions {
185 | return &DebugOptions{
186 | Flags: genericclioptions.NewConfigFlags(),
187 | IOStreams: streams,
188 | PortForwarder: &defaultPortForwarder{
189 | IOStreams: streams,
190 | },
191 | Logger: log.New(streams.Out, "kubectl-debug ", (log.LstdFlags | log.Lshortfile)),
192 | }
193 | }
194 |
195 | // NewDebugCmd returns a cobra command wrapping DebugOptions
196 | func NewDebugCmd(streams genericclioptions.IOStreams) *cobra.Command {
197 | opts := NewDebugOptions(streams)
198 |
199 | cmd := &cobra.Command{
200 | Use: "kubectl-debug --namespace NAMESPACE POD_NAME -c TARGET_CONTAINER_NAME",
201 | DisableFlagsInUseLine: true,
202 | Short: "Launch a debug container, attached to a target container in a running pod",
203 | Long: longDesc,
204 | Example: example,
205 | Version: version.Version(),
206 | Run: func(c *cobra.Command, args []string) {
207 | argsLenAtDash := c.ArgsLenAtDash()
208 | cmdutil.CheckErr(opts.Complete(c, args, argsLenAtDash))
209 | cmdutil.CheckErr(opts.Validate())
210 | cmdutil.CheckErr(opts.Run())
211 | },
212 | }
213 |
214 | cmd.Flags().StringVar(&opts.Image, "image", "",
215 | fmt.Sprintf("the debug container image, default: %s", defaultDebugContainerImage))
216 |
217 | cmd.Flags().StringVar(&opts.RegistrySecretName, "registry-secret-name", "",
218 | fmt.Sprintf("private registry secret name, default: %s", defaultRegistrySecretName))
219 |
220 | cmd.Flags().StringVar(&opts.RegistrySecretNamespace, "registry-secret-namespace", "",
221 | fmt.Sprintf("private registry secret namespace, default: %s", defaultRegistrySecretNamespace))
222 |
223 | cmd.Flags().BoolVar(&opts.RegistrySkipTLSVerify, "registry-skip-tls-verify", false,
224 | fmt.Sprintf("if true, the registry's certificate will not be checked for validity. This will make your HTTPS connections insecure, default: %s", defaultRegistrySkipTLSVerify))
225 |
226 | cmd.Flags().StringSliceVar(&opts.ForkPodRetainLabels, "fork-pod-retain-labels", []string{},
227 | "list of pod labels to retain when in fork mode, default: not set")
228 |
229 | cmd.Flags().StringVarP(&opts.ContainerName, "container", "c", "",
230 | "target container to debug, defaults to the first container in target pod spec")
231 |
232 | cmd.Flags().IntVarP(&opts.AgentPort, "port", "p", 0,
233 | fmt.Sprintf("debug-agent port to which kubectl-debug will connect, default: %d", defaultDebugAgentPort))
234 |
235 | cmd.Flags().StringVar(&opts.ConfigLocation, "configfile", "",
236 | fmt.Sprintf("debug-agent config file (including path), if no config file is present at the specified location then default values are used. Default: %s", filepath.FromSlash(defaultDebugAgentConfigFileLocation)))
237 |
238 | cmd.Flags().BoolVar(&opts.Fork, "fork", false,
239 | "fork a new pod for debugging (useful if the pod status is CrashLoopBackoff)")
240 |
241 | cmd.Flags().BoolVar(&opts.PortForward, "port-forward", true,
242 | fmt.Sprintf("use port-forward to connect from kubectl-debug to debug-agent pod, default: %t", defaultPortForward))
243 |
244 | // it may be that someone has already deployed a daemonset containing with the debug-agent pod and so we can use that (create-debug-agent-pod must be 'false' for this param to be used)
245 | cmd.Flags().StringVar(&opts.DebugAgentDaemonSet, "daemonset-name", opts.DebugAgentDaemonSet,
246 | fmt.Sprintf("debug agent daemonset name when using port-forward, default: %s", defaultDebugAgentDaemonSetName))
247 |
248 | cmd.Flags().StringVar(&opts.DebugAgentNamespace, "debug-agent-namespace", opts.DebugAgentNamespace,
249 | fmt.Sprintf("namespace in which to create the debug-agent pod, default: %s", defaultDebugAgentPodNamespace))
250 |
251 | // flags used for daemonsetless, aka createDebugAgentPod mode.
252 | cmd.Flags().BoolVarP(&opts.CreateDebugAgentPod, "create-debug-agent-pod", "a", true,
253 | fmt.Sprintf("debug-agent pod will be automatically created on the target host, default: %t", defaultCreateDebugAgentPod))
254 |
255 | cmd.Flags().StringVar(&opts.AgentImage, "debug-agent-image", "",
256 | fmt.Sprintf("the image of the debug-agent container, default: %s", defaultDebugAgentImage))
257 |
258 | cmd.Flags().StringVar(&opts.AgentImagePullPolicy, "agent-pull-policy", "",
259 | fmt.Sprintf("the debug-agent container image pull policy, default: %s", defaultDebugAgentImagePullPolicy))
260 |
261 | cmd.Flags().StringVar(&opts.AgentImagePullSecretName, "agent-pull-secret-name", "",
262 | fmt.Sprintf("the debug-agent container image pull secret name, default to empty"))
263 |
264 | cmd.Flags().StringVar(&opts.AgentPodName, "agent-pod-name-prefix", "",
265 | fmt.Sprintf("debug-agent pod name prefix , default to %s", defaultDebugAgentPodNamePrefix))
266 |
267 | cmd.Flags().StringVar(&opts.AgentPodNamespace, "agent-pod-namespace", "",
268 | fmt.Sprintf("agent pod namespace, default: %s", defaultDebugAgentPodNamespace))
269 |
270 | cmd.Flags().StringVar(&opts.AgentPodResource.CpuRequests, "agent-pod-cpu-requests", "",
271 | fmt.Sprintf("agent pod cpu requests, default is not set"))
272 |
273 | cmd.Flags().StringVar(&opts.AgentPodResource.MemoryRequests, "agent-pod-memory-requests", "",
274 | fmt.Sprintf("agent pod memory requests, default is not set"))
275 |
276 | cmd.Flags().StringVar(&opts.AgentPodResource.CpuLimits, "agent-pod-cpu-limits", "",
277 | fmt.Sprintf("agent pod cpu limits, default is not set"))
278 |
279 | cmd.Flags().StringVar(&opts.AgentPodResource.MemoryLimits, "agent-pod-memory-limits", "",
280 | fmt.Sprintf("agent pod memory limits, default is not set"))
281 |
282 | cmd.Flags().BoolVarP(&opts.IsLxcfsEnabled, "enable-lxcfs", "", true,
283 | fmt.Sprintf("Enable Lxcfs, the target container can use its proc files, default: %t", defaultLxcfsEnable))
284 |
285 | cmd.Flags().IntVarP(&opts.Verbosity, "verbosity", "v", 0,
286 | fmt.Sprintf("Set logging verbosity, default: %d", defaultVerbosity))
287 |
288 | opts.Flags.AddFlags(cmd.Flags())
289 |
290 | return cmd
291 | }
292 |
293 | // Complete populate default values from KUBECONFIG file
294 | func (o *DebugOptions) Complete(cmd *cobra.Command, args []string, argsLenAtDash int) error {
295 | o.Args = args
296 | if len(args) == 0 {
297 | return cmdutil.UsageErrorf(cmd, usageError)
298 | }
299 |
300 | var err error
301 | configLoader := o.Flags.ToRawKubeConfigLoader()
302 | o.Namespace, _, err = configLoader.Namespace()
303 | if err != nil {
304 | return err
305 | }
306 |
307 | matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(o.Flags)
308 | f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
309 | o.RESTClient, err = f.RESTClient()
310 | if err != nil {
311 | return err
312 | }
313 |
314 | o.PodName = args[0]
315 |
316 | // read values from config file
317 | configFile := o.ConfigLocation
318 | if len(o.ConfigLocation) < 1 {
319 | if err == nil {
320 | configFile = filepath.FromSlash(defaultDebugAgentConfigFileLocation)
321 | }
322 | }
323 | config, err := LoadFile(configFile)
324 | if err != nil {
325 | if !os.IsNotExist(err) {
326 | // TODO: support verbosity level
327 | fmt.Fprintf(o.ErrOut, "error parsing configuration file: %v", err)
328 | }
329 | config = &Config{}
330 | }
331 |
332 | // combine hardcoded default values, configfile specified values and user cli specified values
333 | o.Command = args[1:]
334 | if len(o.Command) < 1 {
335 | if len(config.Command) > 0 {
336 | o.Command = config.Command
337 | } else {
338 | o.Command = []string{"bash"}
339 | }
340 | }
341 |
342 | if len(o.Image) < 1 {
343 | if len(config.Image) > 0 {
344 | o.Image = config.Image
345 | } else {
346 | o.Image = defaultDebugContainerImage
347 | }
348 | }
349 |
350 | if len(o.RegistrySecretName) < 1 {
351 | if len(config.RegistrySecretName) > 0 {
352 | o.RegistrySecretName = config.RegistrySecretName
353 | } else {
354 | o.RegistrySecretName = defaultRegistrySecretName
355 | }
356 | }
357 |
358 | if len(o.RegistrySecretNamespace) < 1 {
359 | if len(config.RegistrySecretNamespace) > 0 {
360 | o.RegistrySecretNamespace = config.RegistrySecretNamespace
361 | } else {
362 | o.RegistrySecretNamespace = defaultRegistrySecretNamespace
363 | }
364 | }
365 |
366 | if !o.RegistrySkipTLSVerify {
367 | if config.RegistrySkipTLSVerify {
368 | o.RegistrySkipTLSVerify = config.RegistrySkipTLSVerify
369 | } else {
370 | o.RegistrySkipTLSVerify = defaultRegistrySkipTLSVerify
371 | }
372 | }
373 |
374 | if len(o.ForkPodRetainLabels) < 1 {
375 | if len(config.ForkPodRetainLabels) > 0 {
376 | o.ForkPodRetainLabels = config.ForkPodRetainLabels
377 | }
378 | }
379 |
380 | if o.AgentPort < 1 {
381 | if config.AgentPort > 0 {
382 | o.AgentPort = config.AgentPort
383 | } else {
384 | o.AgentPort = defaultDebugAgentPort
385 | }
386 | }
387 |
388 | if len(o.DebugAgentNamespace) < 1 {
389 | if len(config.DebugAgentNamespace) > 0 {
390 | o.DebugAgentNamespace = config.DebugAgentNamespace
391 | } else {
392 | o.DebugAgentNamespace = defaultDebugAgentPodNamespace
393 | }
394 | }
395 |
396 | if len(o.DebugAgentDaemonSet) < 1 {
397 | if len(config.DebugAgentDaemonSet) > 0 {
398 | o.DebugAgentDaemonSet = config.DebugAgentDaemonSet
399 | } else {
400 | o.DebugAgentDaemonSet = defaultDebugAgentDaemonSetName
401 | }
402 | }
403 |
404 | if len(o.AgentPodName) < 1 {
405 | if len(config.AgentPodNamePrefix) > 0 {
406 | o.AgentPodName = config.AgentPodNamePrefix
407 | } else {
408 | o.AgentPodName = defaultDebugAgentPodNamePrefix
409 | }
410 | }
411 |
412 | if len(o.AgentImage) < 1 {
413 | if len(config.AgentImage) > 0 {
414 | o.AgentImage = config.AgentImage
415 | } else {
416 | o.AgentImage = defaultDebugAgentImage
417 | }
418 | }
419 |
420 | if len(o.AgentImagePullPolicy) < 1 {
421 | if len(config.AgentImagePullPolicy) > 0 {
422 | o.AgentImagePullPolicy = config.AgentImagePullPolicy
423 | } else {
424 | o.AgentImagePullPolicy = defaultDebugAgentImagePullPolicy
425 | }
426 | }
427 |
428 | if len(o.AgentImagePullSecretName) < 1 {
429 | if len(config.AgentImagePullSecretName) > 0 {
430 | o.AgentImagePullSecretName = config.AgentImagePullSecretName
431 | } else {
432 | o.AgentImagePullSecretName = defaultDebugAgentImagePullSecretName
433 | }
434 | }
435 |
436 | if len(o.AgentPodNamespace) < 1 {
437 | if len(config.AgentPodNamespace) > 0 {
438 | o.AgentPodNamespace = config.AgentPodNamespace
439 | } else {
440 | o.AgentPodNamespace = defaultDebugAgentPodNamespace
441 | }
442 | }
443 |
444 | if len(o.AgentPodResource.CpuRequests) < 1 {
445 | if len(config.AgentPodCpuRequests) > 0 {
446 | o.AgentPodResource.CpuRequests = config.AgentPodCpuRequests
447 | } else {
448 | o.AgentPodResource.CpuRequests = defaultDebugAgentPodCpuRequests
449 | }
450 | }
451 |
452 | if len(o.AgentPodResource.MemoryRequests) < 1 {
453 | if len(config.AgentPodMemoryRequests) > 0 {
454 | o.AgentPodResource.MemoryRequests = config.AgentPodMemoryRequests
455 | } else {
456 | o.AgentPodResource.MemoryRequests = defaultDebugAgentPodMemoryRequests
457 | }
458 | }
459 |
460 | if len(o.AgentPodResource.CpuLimits) < 1 {
461 | if len(config.AgentPodCpuLimits) > 0 {
462 | o.AgentPodResource.CpuLimits = config.AgentPodCpuLimits
463 | } else {
464 | o.AgentPodResource.CpuLimits = defaultDebugAgentPodCpuLimits
465 | }
466 | }
467 |
468 | if len(o.AgentPodResource.MemoryLimits) < 1 {
469 | if len(config.AgentPodMemoryLimits) > 0 {
470 | o.AgentPodResource.MemoryLimits = config.AgentPodMemoryLimits
471 | } else {
472 | o.AgentPodResource.MemoryLimits = defaultDebugAgentPodMemoryLimits
473 | }
474 | }
475 |
476 | if o.Verbosity < 1 {
477 | if config.Verbosity > 0 {
478 | o.Verbosity = config.Verbosity
479 | } else {
480 | o.Verbosity = defaultVerbosity
481 | }
482 | }
483 |
484 | if !o.IsLxcfsEnabled {
485 | if config.IsLxcfsEnabled {
486 | o.IsLxcfsEnabled = config.IsLxcfsEnabled
487 | } else {
488 | o.IsLxcfsEnabled = defaultLxcfsEnable
489 | }
490 | }
491 |
492 | if !o.CreateDebugAgentPod {
493 | if config.CreateDebugAgentPod {
494 | o.CreateDebugAgentPod = config.CreateDebugAgentPod
495 | } else {
496 | o.CreateDebugAgentPod = defaultCreateDebugAgentPod
497 | }
498 | }
499 |
500 | if !o.PortForward {
501 | if config.PortForward {
502 | o.PortForward = config.PortForward
503 | } else {
504 | o.PortForward = defaultPortForward
505 | }
506 | }
507 |
508 | o.Ports = []string{strconv.Itoa(o.AgentPort)}
509 | o.Config, err = configLoader.ClientConfig()
510 | if err != nil {
511 | return err
512 | }
513 |
514 | o.UserName = "unidentified user"
515 | // cli help for the flags referenced below can be viewed by running
516 | // kubectl options
517 | if o.Flags.Username != nil && len(*o.Flags.Username) > 0 {
518 | // --username : "Username for basic authentication to the API server"
519 | o.UserName = *o.Flags.Username
520 | log.Printf("User name '%v' received from switch --username\r\n", o.UserName)
521 | } else if o.Flags.AuthInfoName != nil && len(*o.Flags.AuthInfoName) > 0 {
522 | // --user : "The name of the kubeconfig user to use"
523 | o.UserName = *o.Flags.AuthInfoName
524 | log.Printf("User name '%v' received from switch --user\r\n", o.UserName)
525 | } else {
526 | rwCfg, err := configLoader.RawConfig()
527 | if err != nil {
528 | log.Printf("Failed to load configuration : %v\r\n", err)
529 | return err
530 | }
531 | var cfgCtxt *cmdapi.Context
532 | if o.Flags.Context != nil && len(*o.Flags.Context) > 0 {
533 | // --context : "The name of the kubeconfig context to use"
534 | cfgCtxt = rwCfg.Contexts[*o.Flags.Context]
535 | log.Printf("Getting user name from kubectl context '%v' received from switch --context\r\n", *o.Flags.Context)
536 | } else {
537 | cfgCtxt = rwCfg.Contexts[rwCfg.CurrentContext]
538 | log.Printf("Getting user name from default kubectl context '%v'\r\n", rwCfg.CurrentContext)
539 | }
540 | o.UserName = cfgCtxt.AuthInfo
541 | log.Printf("User name '%v' received from kubectl context\r\n", o.UserName)
542 | }
543 |
544 | clientset, err := kubernetes.NewForConfig(o.Config)
545 | if err != nil {
546 | return err
547 | }
548 | o.KubeCli = clientset
549 | o.CoreClient = clientset.CoreV1()
550 | o.StopChannel = make(chan struct{}, 1)
551 | o.ReadyChannel = make(chan struct{})
552 | return nil
553 | }
554 |
555 | // Validate validate
556 | func (o *DebugOptions) Validate() error {
557 | if len(o.PodName) == 0 {
558 | return fmt.Errorf("target pod name must be specified")
559 | }
560 | if len(o.Command) == 0 {
561 | return fmt.Errorf("you must specify at least one command for the container")
562 | }
563 | return nil
564 | }
565 |
566 | // TODO: refactor Run() spaghetti code
567 | // Run run
568 | func (o *DebugOptions) Run() error {
569 | pod, err := o.CoreClient.Pods(o.Namespace).Get(o.PodName, v1.GetOptions{})
570 | if err != nil {
571 | o.Logger.Printf("error with pod spec")
572 | return err
573 | }
574 |
575 | containerName := o.ContainerName
576 | if len(containerName) == 0 {
577 | if len(pod.Spec.Containers) > 1 {
578 | usageString := fmt.Sprintf("No container name specified, choosing container: %s.", pod.Spec.Containers[0].Name)
579 | fmt.Fprintf(o.ErrOut, "%s\n\r", usageString)
580 | }
581 | containerName = pod.Spec.Containers[0].Name
582 | }
583 | err = o.auth(pod)
584 | if err != nil {
585 | return err
586 | }
587 | // Launch debug launching pod in createDebugAgentPod mode.
588 | var agentPod *corev1.Pod
589 | if o.CreateDebugAgentPod {
590 | o.AgentPodNode = pod.Spec.NodeName
591 | o.AgentPodName = fmt.Sprintf("%s-%s", o.AgentPodName, uuid.NewUUID())
592 | agentPod = o.getAgentPod()
593 | agentPod, err = o.launchPod(agentPod)
594 | if err != nil {
595 | fmt.Fprintf(o.Out, "the debug-agent pod is not running, you should check the reason, delete any failed debug-agent pod(s) and retry.\r\n")
596 | return err
597 | }
598 | }
599 |
600 | // in fork mode, we launch an new pod as a copy of target pod
601 | // and hack the entry point of the target container with sleep command
602 | // which keeps the container running.
603 | if o.Fork {
604 | // build the fork pod labels
605 | fmt.Fprintf(o.Out, "Forked mode selected\n")
606 | podLabels := o.buildForkPodLabels(pod)
607 | // copy pod and run
608 | pod = copyAndStripPod(pod, containerName, podLabels)
609 | pod, err = o.launchPod(pod)
610 | if err != nil {
611 | fmt.Fprintf(o.Out, "the ForkedPod is not running, you should check the reason and delete the failed ForkedPod and retry\r\n")
612 | o.deleteAgent(agentPod)
613 | return err
614 | }
615 | }
616 |
617 | if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
618 | o.deleteAgent(agentPod)
619 | return fmt.Errorf("cannot debug in a completed pod; current pod phase is %s", pod.Status.Phase)
620 | }
621 |
622 | containerID, err := o.getContainerIDByName(pod, containerName)
623 | if err != nil {
624 | fmt.Fprintf(o.Out, "an error occured, pod is %s, container name is: %s . Will clean up and exit.\r\n", pod, containerName)
625 | o.deleteAgent(agentPod)
626 | return err
627 | }
628 |
629 | t := o.setupTTY()
630 | var sizeQueue remotecommand.TerminalSizeQueue
631 | if t.Raw {
632 | // this call spawns a goroutine to monitor/update the terminal size
633 | sizeQueue = t.MonitorSize(t.GetSize())
634 | // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
635 | // true
636 | // o.ErrOut = nil
637 | }
638 |
639 | if o.PortForward {
640 | var agent *corev1.Pod
641 | if !o.CreateDebugAgentPod {
642 | // See if there is a debug-agent pod running as a daemonset
643 | o.Logger.Printf("See if there is a debug-agent pod running in a daemonset. daemonset '%v' from namespace %v\r\n", o.DebugAgentDaemonSet, o.DebugAgentNamespace)
644 |
645 | daemonSet, err := o.KubeCli.AppsV1().DaemonSets(o.DebugAgentNamespace).Get(o.DebugAgentDaemonSet, v1.GetOptions{})
646 | if err != nil {
647 | return err
648 | }
649 | labelSet := labels.Set(daemonSet.Spec.Selector.MatchLabels)
650 | agents, err := o.CoreClient.Pods(o.DebugAgentNamespace).List(v1.ListOptions{
651 | LabelSelector: labelSet.String(),
652 | })
653 | if err != nil {
654 | return err
655 | }
656 | for i := range agents.Items {
657 | if agents.Items[i].Spec.NodeName == pod.Spec.NodeName {
658 | agent = &agents.Items[i]
659 | break
660 | }
661 | }
662 | } else {
663 | agent = agentPod
664 | }
665 |
666 | if agent == nil {
667 | return fmt.Errorf("there is no debug-agent pod running on the same node as your target pod %s\r\n", o.PodName)
668 | }
669 | if o.Verbosity > 0 {
670 | fmt.Fprintf(o.Out, "target pod: %s target pod IP: %s, debug-agent pod IP: %s\r\n", o.PodName, pod.Status.PodIP, agent.Status.HostIP)
671 | }
672 | err = o.runPortForward(agent)
673 | if err != nil {
674 | fmt.Fprintf(o.Out, "an error has occured, will delete debug-agent pod and exit\r\n")
675 | o.deleteAgent(agentPod)
676 | return err
677 | }
678 | // client can't access the node ip in the k8s cluster sometimes,
679 | // than we use forward ports to connect the specified pod and that will listen
680 | // on specified ports in localhost, the ports can not access until receive the
681 | // ready signal
682 | if o.Verbosity > 0 {
683 | fmt.Fprintln(o.Out, "using port-forwarding. Waiting for port-forward connection with debug-agent...\r\n")
684 | }
685 | <-o.ReadyChannel
686 | }
687 |
688 | fn := func() error {
689 | // TODO: refactor as kubernetes api style, reuse rbac mechanism of kubernetes
690 | var targetHost string
691 | if o.PortForward {
692 | targetHost = "localhost"
693 | } else {
694 | targetHost = pod.Status.HostIP
695 | }
696 | uri, err := url.Parse(fmt.Sprintf("http://%s:%d", targetHost, o.AgentPort))
697 | if err != nil {
698 | o.Logger.Printf("error parsing url http://%s:%d", targetHost, o.AgentPort)
699 | return err
700 | }
701 | uri.Path = fmt.Sprintf("/api/v1/debug")
702 | params := url.Values{}
703 | params.Add("image", o.Image)
704 | params.Add("container", containerID)
705 | params.Add("verbosity", fmt.Sprintf("%v", o.Verbosity))
706 | hstNm, _ := os.Hostname()
707 | params.Add("hostname", hstNm)
708 | params.Add("username", o.UserName)
709 | if o.IsLxcfsEnabled {
710 | params.Add("lxcfsEnabled", "true")
711 | } else {
712 | params.Add("lxcfsEnabled", "false")
713 | }
714 | if o.RegistrySkipTLSVerify {
715 | params.Add("registrySkipTLS", "true")
716 | } else {
717 | params.Add("registrySkipTLS", "false")
718 | }
719 | var authStr string
720 | registrySecret, err := o.CoreClient.Secrets(o.RegistrySecretNamespace).Get(o.RegistrySecretName, v1.GetOptions{})
721 | if err != nil {
722 | if errors.IsNotFound(err) {
723 | if o.Verbosity > 0 {
724 | o.Logger.Printf("Secret: %v not found in namespace: %v\r\n", o.RegistrySecretName, o.RegistrySecretNamespace)
725 | }
726 | authStr = ""
727 | } else {
728 | return err
729 | }
730 | } else {
731 | if o.Verbosity > 1 {
732 | o.Logger.Printf("Found secret: %v:%v\r\n", o.RegistrySecretNamespace, o.RegistrySecretName)
733 | }
734 | authStr, _ = o.extractSecret(registrySecret.Data)
735 | }
736 | params.Add("authStr", authStr)
737 | commandBytes, err := json.Marshal(o.Command)
738 | if err != nil {
739 | return err
740 | }
741 | params.Add("command", string(commandBytes))
742 | uri.RawQuery = params.Encode()
743 | return o.remoteExecute("POST", uri, o.Config, o.In, o.Out, o.ErrOut, t.Raw, sizeQueue)
744 | }
745 |
746 | // ensure debug pod is deleted
747 | withCleanUp := func() error {
748 | return interrupt.Chain(nil, func() {
749 | if o.Fork {
750 | fmt.Fprintf(o.Out, "deleting forked pod: %s \n\r", pod.Name)
751 | err := o.CoreClient.Pods(pod.Namespace).Delete(pod.Name, v1.NewDeleteOptions(0))
752 | if err != nil {
753 | // we may leak pod here, but we have nothing to do except notify the user
754 | fmt.Fprintf(o.ErrOut, "failed to delete forked pod: %s Namespace: %s, you may have to manually delete the pod.\n\r", pod.Name, pod.Namespace)
755 | }
756 | }
757 |
758 | if o.PortForward {
759 | // close the port-forward
760 | if o.StopChannel != nil {
761 | close(o.StopChannel)
762 | }
763 | }
764 | // delete agent pod
765 | if o.CreateDebugAgentPod && agentPod != nil {
766 | fmt.Fprintf(o.Out, "\n\rdeleting debug-agent pod\n\r")
767 | o.deleteAgent(agentPod)
768 | }
769 | }).Run(fn)
770 | }
771 |
772 | if err := t.Safe(withCleanUp); err != nil {
773 | fmt.Fprintf(o.Out, "an error occured executing remote command(s), %v\r\n", err)
774 | return err
775 | }
776 | o.wait.Wait()
777 | return nil
778 | }
779 |
780 | func (o *DebugOptions) extractSecret(scrtDta map[string][]byte) (string, error) {
781 | var ret []byte
782 | ret = scrtDta["authStr"]
783 | if len(ret) == 0 {
784 | // In IKS ( IBM Kubernetes ) the secret is stored in a json blob with the key '.dockerconfigjson'
785 | // The json has the form
786 | // {"auths":{"":{"username":"iamapikey","password":"","email":"iamapikey","auth":""}}}
787 | // Where would be one of the public domain names values here
788 | // https://cloud.ibm.com/docs/Registry?topic=registry-registry_overview#registry_regions_local
789 | // e.g. us.icr.io
790 | ret = scrtDta[".dockerconfigjson"]
791 | if len(ret) == 0 {
792 | return "", nil
793 | } else if o.Verbosity > 0 {
794 | o.Logger.Printf("Found secret with key .dockerconfigjson\r\n")
795 | }
796 |
797 | var dta map[string]interface{}
798 | if err := json.Unmarshal(ret, &dta); err != nil {
799 | o.Logger.Printf("Failed to parse .dockerconfigjson value: %v\r\n", err)
800 | return "", err
801 | } else {
802 | dta = dta["auths"].(map[string]interface{})
803 | // Under auths there will be a value stored with the region key. e.g. "us.icr.io"
804 | for _, v := range dta {
805 | dta = v.(map[string]interface{})
806 | break
807 | }
808 | sret := dta["auth"].(string)
809 | ret, err = base64.StdEncoding.DecodeString(sret)
810 | if err != nil {
811 | o.Logger.Printf("Failed to base 64 decode auth value : %v\r\n", err)
812 | return "", err
813 | }
814 | }
815 | } else if o.Verbosity > 0 {
816 | o.Logger.Println("Found secret with key authStr")
817 | }
818 | return string(ret), nil
819 | }
820 |
821 | func (o *DebugOptions) getContainerIDByName(pod *corev1.Pod, containerName string) (string, error) {
822 | for _, containerStatus := range pod.Status.ContainerStatuses {
823 | if containerStatus.Name != containerName {
824 | continue
825 | }
826 | // #52 if a pod is running but not ready(because of readiness probe), we can connect
827 | if containerStatus.State.Running == nil {
828 | return "", fmt.Errorf("container [%s] not running", containerName)
829 | }
830 | if o.Verbosity > 0 {
831 | o.Logger.Printf("Getting id from containerStatus %+v\r\n", containerStatus)
832 | }
833 | return containerStatus.ContainerID, nil
834 | }
835 |
836 | // #14 otherwise we should search for running init containers
837 | for _, initContainerStatus := range pod.Status.InitContainerStatuses {
838 | if initContainerStatus.Name != containerName {
839 | continue
840 | }
841 | if initContainerStatus.State.Running == nil {
842 | return "", fmt.Errorf("init container [%s] is not running", containerName)
843 | }
844 | if o.Verbosity > 0 {
845 | o.Logger.Printf("Getting id from initContainerStatus %+v\r\n", initContainerStatus)
846 | }
847 | return initContainerStatus.ContainerID, nil
848 | }
849 |
850 | return "", fmt.Errorf("cannot find specified container %s", containerName)
851 | }
852 |
853 | func (o *DebugOptions) remoteExecute(
854 | method string,
855 | url *url.URL,
856 | config *restclient.Config,
857 | stdin io.Reader,
858 | stdout, stderr io.Writer,
859 | tty bool,
860 | terminalSizeQueue remotecommand.TerminalSizeQueue) error {
861 |
862 | if o.Verbosity > 0 {
863 | o.Logger.Printf("Creating SPDY executor %+v %+v %+v\r\n", config, method, url)
864 | }
865 | exec, err := remotecommand.NewSPDYExecutor(config, method, url)
866 | if err != nil {
867 | o.Logger.Printf("Error creating SPDY executor.\r\n")
868 | return err
869 | }
870 | if o.Verbosity > 0 {
871 | o.Logger.Printf("Creating exec Stream\r\n")
872 | }
873 | return exec.Stream(remotecommand.StreamOptions{
874 | Stdin: stdin,
875 | Stdout: stdout,
876 | Stderr: stderr,
877 | Tty: tty,
878 | TerminalSizeQueue: terminalSizeQueue,
879 | })
880 | }
881 |
882 | func (o *DebugOptions) setupTTY() term.TTY {
883 | t := term.TTY{
884 | Out: o.Out,
885 | }
886 | t.In = o.In
887 | t.Raw = true
888 | if !t.IsTerminalIn() {
889 | if o.ErrOut != nil {
890 | fmt.Fprintln(o.ErrOut, "Unable to use a TTY - input is not a terminal or the right kind of file\r\n")
891 | }
892 | return t
893 | }
894 | stdin, stdout, _ := dockerterm.StdStreams()
895 | o.In = stdin
896 | t.In = stdin
897 | if o.Out != nil {
898 | o.Out = stdout
899 | t.Out = stdout
900 | }
901 | return t
902 | }
903 |
904 | func (o *DebugOptions) buildForkPodLabels(pod *corev1.Pod) map[string]string {
905 | podLabels := map[string]string{}
906 | for _, label := range o.ForkPodRetainLabels {
907 | for k, v := range pod.ObjectMeta.Labels {
908 | if label == k {
909 | podLabels[k] = v
910 | }
911 | }
912 | }
913 | return podLabels
914 | }
915 |
916 | // copyAndStripPod copy the given pod template, strip the probes and labels,
917 | // and replace the entry point
918 | func copyAndStripPod(pod *corev1.Pod, targetContainer string, podLabels map[string]string) *corev1.Pod {
919 | copied := &corev1.Pod{
920 | ObjectMeta: *pod.ObjectMeta.DeepCopy(),
921 | Spec: *pod.Spec.DeepCopy(),
922 | }
923 | // Using original pod name + xid + debug ad copied pod name. To ensure a
924 | // valid pod name we truncate original pod name to keep the total chars <64
925 | copied.Name = fmt.Sprintf("%.34s-%s-debug", pod.Name, xid.New().String())
926 | copied.Labels = podLabels
927 | copied.Spec.RestartPolicy = corev1.RestartPolicyNever
928 | for i, c := range copied.Spec.Containers {
929 | copied.Spec.Containers[i].LivenessProbe = nil
930 | copied.Spec.Containers[i].ReadinessProbe = nil
931 | if c.Name == targetContainer {
932 | // Hack, infinite sleep command to keep the container running
933 | copied.Spec.Containers[i].Command = []string{"sh", "-c", "--"}
934 | copied.Spec.Containers[i].Args = []string{"while true; do sleep 30; done;"}
935 | }
936 | }
937 | copied.ResourceVersion = ""
938 | copied.UID = ""
939 | copied.SelfLink = ""
940 | copied.CreationTimestamp = v1.Time{}
941 | copied.OwnerReferences = []v1.OwnerReference{}
942 |
943 | return copied
944 | }
945 |
946 | // launchPod launch given pod until it's running
947 | func (o *DebugOptions) launchPod(pod *corev1.Pod) (*corev1.Pod, error) {
948 | pod, err := o.CoreClient.Pods(pod.Namespace).Create(pod)
949 | if err != nil {
950 | o.Logger.Printf("error with launch pod")
951 | return pod, err
952 | }
953 |
954 | watcher, err := o.CoreClient.Pods(pod.Namespace).Watch(v1.SingleObject(pod.ObjectMeta))
955 | if err != nil {
956 | o.Logger.Printf("error with watching pod")
957 | return nil, err
958 | }
959 | // FIXME: hard code -> config
960 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
961 | defer cancel()
962 | fmt.Fprintf(o.Out, "Waiting for pod %s to run...\n", pod.Name)
963 | event, err := watch.UntilWithoutRetry(ctx, watcher, conditions.PodRunning)
964 | if err != nil {
965 | fmt.Fprintf(o.ErrOut, "Error occurred while waiting for pod to run: %v\r\n", err)
966 | return nil, err
967 | }
968 | pod = event.Object.(*corev1.Pod)
969 | return pod, nil
970 | }
971 |
972 | // getAgentPod construct debug-agent pod template
973 | func (o *DebugOptions) getAgentPod() *corev1.Pod {
974 | prop := corev1.MountPropagationBidirectional
975 | directoryCreate := corev1.HostPathDirectoryOrCreate
976 | priveleged := true
977 | agentPod := &corev1.Pod{
978 | TypeMeta: v1.TypeMeta{
979 | Kind: "Pod",
980 | APIVersion: "v1",
981 | },
982 | ObjectMeta: v1.ObjectMeta{
983 | Name: o.AgentPodName,
984 | Namespace: o.AgentPodNamespace,
985 | },
986 | Spec: corev1.PodSpec{
987 | HostPID: true,
988 | NodeName: o.AgentPodNode,
989 | ImagePullSecrets: []corev1.LocalObjectReference{
990 | {
991 | Name: o.AgentImagePullSecretName,
992 | },
993 | },
994 | Containers: []corev1.Container{
995 | {
996 | Name: "debug-agent",
997 | Image: o.AgentImage,
998 | ImagePullPolicy: corev1.PullPolicy(o.AgentImagePullPolicy),
999 | LivenessProbe: &corev1.Probe{
1000 | Handler: corev1.Handler{
1001 | HTTPGet: &corev1.HTTPGetAction{
1002 | Path: "/healthz",
1003 | Port: intstr.FromInt(10027),
1004 | },
1005 | },
1006 | InitialDelaySeconds: 10,
1007 | PeriodSeconds: 10,
1008 | SuccessThreshold: 1,
1009 | TimeoutSeconds: 1,
1010 | FailureThreshold: 3,
1011 | },
1012 | SecurityContext: &corev1.SecurityContext{
1013 | Privileged: &priveleged,
1014 | },
1015 | Resources: o.buildAgentResourceRequirements(),
1016 | VolumeMounts: []corev1.VolumeMount{
1017 | {
1018 | Name: "docker",
1019 | MountPath: "/var/run/docker.sock",
1020 | },
1021 | {
1022 | Name: "cgroup",
1023 | MountPath: "/sys/fs/cgroup",
1024 | },
1025 | // containerd client needs to access /var/data, /run/containerd, /var/lib/containerd and /run/runc
1026 | {
1027 | Name: "vardata",
1028 | MountPath: "/var/data",
1029 | },
1030 | {
1031 | Name: "varlibcontainerd",
1032 | MountPath: "/var/lib/containerd",
1033 | },
1034 | {
1035 | Name: "runcontainerd",
1036 | MountPath: "/run/containerd",
1037 | },
1038 | {
1039 | Name: "runrunc",
1040 | MountPath: "/run/runc",
1041 | },
1042 | {
1043 | Name: "lxcfs",
1044 | MountPath: "/var/lib/lxc",
1045 | MountPropagation: &prop,
1046 | },
1047 | },
1048 | Ports: []corev1.ContainerPort{
1049 | {
1050 | Name: "http",
1051 | HostPort: int32(o.AgentPort),
1052 | ContainerPort: 10027,
1053 | },
1054 | },
1055 | },
1056 | },
1057 | Volumes: []corev1.Volume{
1058 | {
1059 | Name: "docker",
1060 | VolumeSource: corev1.VolumeSource{
1061 | HostPath: &corev1.HostPathVolumeSource{
1062 | Path: "/var/run/docker.sock",
1063 | },
1064 | },
1065 | },
1066 | {
1067 | Name: "cgroup",
1068 | VolumeSource: corev1.VolumeSource{
1069 | HostPath: &corev1.HostPathVolumeSource{
1070 | Path: "/sys/fs/cgroup",
1071 | },
1072 | },
1073 | },
1074 | {
1075 | Name: "lxcfs",
1076 | VolumeSource: corev1.VolumeSource{
1077 | HostPath: &corev1.HostPathVolumeSource{
1078 | Path: "/var/lib/lxc",
1079 | Type: &directoryCreate,
1080 | },
1081 | },
1082 | },
1083 | {
1084 | Name: "vardata",
1085 | VolumeSource: corev1.VolumeSource{
1086 | HostPath: &corev1.HostPathVolumeSource{
1087 | Path: "/var/data",
1088 | },
1089 | },
1090 | },
1091 | {
1092 | Name: "runcontainerd",
1093 | VolumeSource: corev1.VolumeSource{
1094 | HostPath: &corev1.HostPathVolumeSource{
1095 | Path: "/run/containerd",
1096 | },
1097 | },
1098 | },
1099 | {
1100 | Name: "varlibcontainerd",
1101 | VolumeSource: corev1.VolumeSource{
1102 | HostPath: &corev1.HostPathVolumeSource{
1103 | Path: "/var/lib/containerd",
1104 | },
1105 | },
1106 | },
1107 | {
1108 | Name: "runrunc",
1109 | VolumeSource: corev1.VolumeSource{
1110 | HostPath: &corev1.HostPathVolumeSource{
1111 | Path: "/run/runc",
1112 | },
1113 | },
1114 | },
1115 | },
1116 | RestartPolicy: corev1.RestartPolicyNever,
1117 | },
1118 | }
1119 | fmt.Fprintf(o.Out, "Agent Pod info: [Name:%s, Namespace:%s, Image:%s, HostPort:%d, ContainerPort:%d]\n", agentPod.ObjectMeta.Name, agentPod.ObjectMeta.Namespace, agentPod.Spec.Containers[0].Image, agentPod.Spec.Containers[0].Ports[0].HostPort, agentPod.Spec.Containers[0].Ports[0].ContainerPort)
1120 | return agentPod
1121 | }
1122 |
1123 | func (o *DebugOptions) runPortForward(pod *corev1.Pod) error {
1124 | if pod.Status.Phase != corev1.PodRunning {
1125 | return fmt.Errorf("unable to forward port because pod is not running. Current status=%v\r\n", pod.Status.Phase)
1126 | }
1127 | o.wait.Add(1)
1128 | go func() {
1129 | defer o.wait.Done()
1130 | req := o.RESTClient.Post().
1131 | Resource("pods").
1132 | Namespace(pod.Namespace).
1133 | Name(pod.Name).
1134 | SubResource("portforward")
1135 | err := o.PortForwarder.ForwardPorts("POST", req.URL(), o)
1136 | if err != nil {
1137 | log.Printf("PortForwarded failed with %+v\r\n", err)
1138 | log.Printf("Sending ready signal just in case the failure reason is that the port is already forwarded.\r\n")
1139 | o.ReadyChannel <- struct{}{}
1140 | }
1141 | if o.Verbosity > 0 {
1142 | fmt.Fprintln(o.Out, "end port-forward...")
1143 | }
1144 | }()
1145 | return nil
1146 | }
1147 |
1148 | type portForwarder interface {
1149 | ForwardPorts(method string, url *url.URL, opts *DebugOptions) error
1150 | }
1151 |
1152 | type defaultPortForwarder struct {
1153 | genericclioptions.IOStreams
1154 | }
1155 |
1156 | // ForwardPorts forward ports
1157 | func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts *DebugOptions) error {
1158 | transport, upgrader, err := spdy.RoundTripperFor(opts.Config)
1159 | if err != nil {
1160 | opts.Logger.Printf("error with setting up spdy forwarder")
1161 | return err
1162 | }
1163 | dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
1164 | fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut)
1165 | if err != nil {
1166 | opts.Logger.Printf("error with NewDialer")
1167 | return err
1168 | }
1169 | return fw.ForwardPorts()
1170 | }
1171 |
1172 | // auth checks if current user has permission to create pods/exec subresource.
1173 | func (o *DebugOptions) auth(pod *corev1.Pod) error {
1174 | sarClient := o.KubeCli.AuthorizationV1()
1175 | sar := &authorizationv1.SelfSubjectAccessReview{
1176 | Spec: authorizationv1.SelfSubjectAccessReviewSpec{
1177 | ResourceAttributes: &authorizationv1.ResourceAttributes{
1178 | Namespace: pod.Namespace,
1179 | Verb: "create",
1180 | Group: "",
1181 | Resource: "pods",
1182 | Subresource: "exec",
1183 | Name: "",
1184 | },
1185 | },
1186 | }
1187 | response, err := sarClient.SelfSubjectAccessReviews().Create(sar)
1188 | if err != nil {
1189 | fmt.Fprintf(o.ErrOut, "Failed to create SelfSubjectAccessReview: %v \r\n", err)
1190 | return err
1191 | }
1192 | if !response.Status.Allowed {
1193 | denyReason := fmt.Sprintf("Current user has no permission to create pods/exec subresource in namespace:%s. Detail:", pod.Namespace)
1194 | if len(response.Status.Reason) > 0 {
1195 | denyReason = fmt.Sprintf("%s %v, ", denyReason, response.Status.Reason)
1196 | }
1197 | if len(response.Status.EvaluationError) > 0 {
1198 | denyReason = fmt.Sprintf("%s %v", denyReason, response.Status.EvaluationError)
1199 | }
1200 | return fmt.Errorf(denyReason)
1201 | }
1202 | return nil
1203 | }
1204 |
1205 | // delete the agent pod
1206 | func (o *DebugOptions) deleteAgent(agentPod *corev1.Pod) {
1207 | // only if createDebugAgentPod=true should we manage the debug-agent pod
1208 | if !o.CreateDebugAgentPod {
1209 | return
1210 | }
1211 | err := o.CoreClient.Pods(agentPod.Namespace).Delete(agentPod.Name, v1.NewDeleteOptions(0))
1212 | if err != nil {
1213 | fmt.Fprintf(o.ErrOut, "failed to delete agent pod[Name:%s, Namespace: %s], consider manual deletion.\r\nerror msg: %v", agentPod.Name, agentPod.Namespace, err)
1214 | }
1215 | }
1216 |
1217 | // build the debug-agent pod Resource Requirements
1218 | func (o *DebugOptions) buildAgentResourceRequirements() corev1.ResourceRequirements {
1219 | return getResourceRequirements(getResourceList(o.AgentPodResource.CpuRequests, o.AgentPodResource.MemoryRequests), getResourceList(o.AgentPodResource.CpuLimits, o.AgentPodResource.MemoryLimits))
1220 | }
1221 |
1222 | func getResourceList(cpu, memory string) corev1.ResourceList {
1223 | // catch error in resource.MustParse
1224 | defer func() {
1225 | if err := recover(); err != nil {
1226 | fmt.Printf("Parse Resource list error: %v\n", err)
1227 | }
1228 | }()
1229 | res := corev1.ResourceList{}
1230 | if cpu != "" {
1231 | res[corev1.ResourceCPU] = resource.MustParse(cpu)
1232 | }
1233 | if memory != "" {
1234 | res[corev1.ResourceMemory] = resource.MustParse(memory)
1235 | }
1236 | return res
1237 | }
1238 |
1239 | func getResourceRequirements(requests, limits corev1.ResourceList) corev1.ResourceRequirements {
1240 | res := corev1.ResourceRequirements{}
1241 | res.Requests = requests
1242 | res.Limits = limits
1243 | return res
1244 | }
1245 |
--------------------------------------------------------------------------------
/pkg/kubectl-debug/config.go:
--------------------------------------------------------------------------------
1 | package kubectldebug
2 |
3 | import (
4 | "gopkg.in/yaml.v2"
5 | "io/ioutil"
6 | )
7 |
8 | type Config struct {
9 | AgentPort int `yaml:"agentPort,omitempty"`
10 | Image string `yaml:"image,omitempty"`
11 | RegistrySecretName string `yaml:"registrySecretName,omitempty"`
12 | RegistrySecretNamespace string `yaml:"registrySecretNamespace,omitempty"`
13 | RegistrySkipTLSVerify bool `yaml:"registrySkipTLSVerify,omitempty"`
14 | ForkPodRetainLabels []string `yaml:"forkPodRetainLabels,omitempty"`
15 | DebugAgentDaemonSet string `yaml:"debugAgentDaemonset,omitempty"`
16 | DebugAgentNamespace string `yaml:"debugAgentNamespace,omitempty"`
17 | Command []string `yaml:"command,omitempty"`
18 | PortForward bool `yaml:"portForward,omitempty"`
19 | CreateDebugAgentPod bool `yaml:"createDebugAgentPod,omitempty"`
20 | AgentPodNamePrefix string `yaml:"agentPodNamePrefix,omitempty"`
21 | AgentPodNamespace string `yaml:"agentPodNamespace,omitempty"`
22 | AgentImage string `yaml:"agentImage,omitempty"`
23 | AgentImagePullPolicy string `yaml:"agentImagePullPolicy,omitempty"`
24 | AgentImagePullSecretName string `yaml:"agentImagePullSecretName,omitempty"`
25 | AgentPodCpuRequests string `yaml:"agentCpuRequests,omitempty"`
26 | AgentPodMemoryRequests string `yaml:"agentMemoryRequests,omitempty"`
27 | AgentPodCpuLimits string `yaml:"agentCpuLimits,omitempty"`
28 | AgentPodMemoryLimits string `yaml:"agentMemoryLimits,omitempty"`
29 | IsLxcfsEnabled bool `yaml:"isLxcfsEnabled,omitempty"`
30 | Verbosity int `yaml:"verbosity,omitempty"`
31 | }
32 |
33 | func Load(s string) (*Config, error) {
34 | cfg := &Config{}
35 | err := yaml.Unmarshal([]byte(s), cfg)
36 | if err != nil {
37 | return nil, err
38 | }
39 | return cfg, nil
40 | }
41 |
42 | func LoadFile(filename string) (*Config, error) {
43 | c, err := ioutil.ReadFile(filename)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return Load(string(c))
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/nsenter/nsenter.go:
--------------------------------------------------------------------------------
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 int64 // 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.FormatInt(cli.Target, 10))
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 | }
62 |
--------------------------------------------------------------------------------
/pkg/util/jsonstream.go:
--------------------------------------------------------------------------------
1 | // copied from docker/docker/pkg/jsonmessage/jsonmesage.go, fix the row align
2 | package term
3 |
4 | import (
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "os"
9 | "strings"
10 | "time"
11 |
12 | gotty "github.com/Nvveen/Gotty"
13 | "github.com/docker/docker/pkg/term"
14 | units "github.com/docker/go-units"
15 | )
16 |
17 | // RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
18 | // ensure the formatted time isalways the same number of characters.
19 | const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
20 |
21 | // JSONError wraps a concrete Code and Message, `Code` is
22 | // is an integer error code, `Message` is the error message.
23 | type JSONError struct {
24 | Code int `json:"code,omitempty"`
25 | Message string `json:"message,omitempty"`
26 | }
27 |
28 | func (e *JSONError) Error() string {
29 | return e.Message
30 | }
31 |
32 | // JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
33 | // Start is the initial value for the operation. Current is the current status and
34 | // value of the progress made towards Total. Total is the end value describing when
35 | // we made 100% progress for an operation.
36 | type JSONProgress struct {
37 | terminalFd uintptr
38 | Current int64 `json:"current,omitempty"`
39 | Total int64 `json:"total,omitempty"`
40 | Start int64 `json:"start,omitempty"`
41 | // If true, don't show xB/yB
42 | HideCounts bool `json:"hidecounts,omitempty"`
43 | Units string `json:"units,omitempty"`
44 | }
45 |
46 | func (p *JSONProgress) String() string {
47 | var (
48 | width = 200
49 | pbBox string
50 | numbersBox string
51 | timeLeftBox string
52 | )
53 |
54 | ws, err := term.GetWinsize(p.terminalFd)
55 | if err == nil {
56 | width = int(ws.Width)
57 | }
58 |
59 | if p.Current <= 0 && p.Total <= 0 {
60 | return ""
61 | }
62 | if p.Total <= 0 {
63 | switch p.Units {
64 | case "":
65 | current := units.HumanSize(float64(p.Current))
66 | return fmt.Sprintf("%8v", current)
67 | default:
68 | return fmt.Sprintf("%d %s", p.Current, p.Units)
69 | }
70 | }
71 |
72 | percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
73 | if percentage > 50 {
74 | percentage = 50
75 | }
76 | if width > 110 {
77 | // this number can't be negative gh#7136
78 | numSpaces := 0
79 | if 50-percentage > 0 {
80 | numSpaces = 50 - percentage
81 | }
82 | pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
83 | }
84 |
85 | switch {
86 | case p.HideCounts:
87 | case p.Units == "": // no units, use bytes
88 | current := units.HumanSize(float64(p.Current))
89 | total := units.HumanSize(float64(p.Total))
90 |
91 | numbersBox = fmt.Sprintf("%8v/%v", current, total)
92 |
93 | if p.Current > p.Total {
94 | // remove total display if the reported current is wonky.
95 | numbersBox = fmt.Sprintf("%8v", current)
96 | }
97 | default:
98 | numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units)
99 |
100 | if p.Current > p.Total {
101 | // remove total display if the reported current is wonky.
102 | numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units)
103 | }
104 | }
105 |
106 | if p.Current > 0 && p.Start > 0 && percentage < 50 {
107 | fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0))
108 | perEntry := fromStart / time.Duration(p.Current)
109 | left := time.Duration(p.Total-p.Current) * perEntry
110 | left = (left / time.Second) * time.Second
111 |
112 | if width > 50 {
113 | timeLeftBox = " " + left.String()
114 | }
115 | }
116 | return pbBox + numbersBox + timeLeftBox
117 | }
118 |
119 | // JSONMessage defines a message struct. It describes
120 | // the created time, where it from, status, ID of the
121 | // message. It's used for docker events.
122 | type JSONMessage struct {
123 | Stream string `json:"stream,omitempty"`
124 | Status string `json:"status,omitempty"`
125 | Progress *JSONProgress `json:"progressDetail,omitempty"`
126 | ProgressMessage string `json:"progress,omitempty"` //deprecated
127 | ID string `json:"id,omitempty"`
128 | From string `json:"from,omitempty"`
129 | Time int64 `json:"time,omitempty"`
130 | TimeNano int64 `json:"timeNano,omitempty"`
131 | Error *JSONError `json:"errorDetail,omitempty"`
132 | ErrorMessage string `json:"error,omitempty"` //deprecated
133 | // Aux contains out-of-band data, such as digests for push signing and image id after building.
134 | Aux *json.RawMessage `json:"aux,omitempty"`
135 | }
136 |
137 | /* Satisfied by gotty.TermInfo as well as noTermInfo from below */
138 | type termInfo interface {
139 | Parse(attr string, params ...interface{}) (string, error)
140 | }
141 |
142 | type noTermInfo struct{} // canary used when no terminfo.
143 |
144 | func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) {
145 | return "", fmt.Errorf("noTermInfo")
146 | }
147 |
148 | func clearLine(out io.Writer, ti termInfo) {
149 | // el2 (clear whole line) is not exposed by terminfo.
150 |
151 | // First clear line from beginning to cursor
152 | if attr, err := ti.Parse("el1"); err == nil {
153 | fmt.Fprintf(out, "%s", attr)
154 | } else {
155 | fmt.Fprintf(out, "\x1b[1K")
156 | }
157 | // Then clear line from cursor to end
158 | if attr, err := ti.Parse("el"); err == nil {
159 | fmt.Fprintf(out, "%s", attr)
160 | } else {
161 | fmt.Fprintf(out, "\x1b[K")
162 | }
163 | }
164 |
165 | func cursorUp(out io.Writer, ti termInfo, l int) {
166 | if l == 0 { // Should never be the case, but be tolerant
167 | return
168 | }
169 | if attr, err := ti.Parse("cuu", l); err == nil {
170 | fmt.Fprintf(out, "%s", attr)
171 | } else {
172 | fmt.Fprintf(out, "\x1b[%dA", l)
173 | }
174 | }
175 |
176 | func cursorDown(out io.Writer, ti termInfo, l int) {
177 | if l == 0 { // Should never be the case, but be tolerant
178 | return
179 | }
180 | if attr, err := ti.Parse("cud", l); err == nil {
181 | fmt.Fprintf(out, "%s", attr)
182 | } else {
183 | fmt.Fprintf(out, "\x1b[%dB", l)
184 | }
185 | }
186 |
187 | // Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out`
188 | // is a terminal. If this is the case, it will erase the entire current line
189 | // when displaying the progressbar.
190 | func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error {
191 | if jm.Error != nil {
192 | if jm.Error.Code == 401 {
193 | return fmt.Errorf("authentication is required")
194 | }
195 | return jm.Error
196 | }
197 | endl := "\r"
198 | if termInfo != nil && jm.Stream == "" && jm.Progress != nil {
199 | clearLine(out, termInfo)
200 | fmt.Fprint(out, endl)
201 | } else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
202 | return nil
203 | }
204 | if jm.TimeNano != 0 {
205 | fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed))
206 | } else if jm.Time != 0 {
207 | fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed))
208 | }
209 | if jm.ID != "" {
210 | fmt.Fprintf(out, "%s: ", jm.ID)
211 | }
212 | if jm.From != "" {
213 | fmt.Fprintf(out, "(from %s) ", jm.From)
214 | }
215 | if jm.Progress != nil && termInfo != nil {
216 | fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
217 | } else if jm.ProgressMessage != "" { //deprecated
218 | fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
219 | } else if jm.Stream != "" {
220 | fmt.Fprintf(out, "%s%s", jm.Stream, endl)
221 | } else {
222 | fmt.Fprintf(out, "%s\n%s", jm.Status, endl)
223 | }
224 | return nil
225 | }
226 |
227 | // DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
228 | // describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
229 | // each line and move the cursor while displaying.
230 | func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error {
231 | var (
232 | dec = json.NewDecoder(in)
233 | ids = make(map[string]int)
234 | )
235 |
236 | var termInfo termInfo
237 |
238 | if isTerminal {
239 | term := os.Getenv("TERM")
240 | if term == "" {
241 | term = "vt102"
242 | }
243 |
244 | var err error
245 | if termInfo, err = gotty.OpenTermInfo(term); err != nil {
246 | termInfo = &noTermInfo{}
247 | }
248 | }
249 |
250 | for {
251 | diff := 0
252 | var jm JSONMessage
253 | if err := dec.Decode(&jm); err != nil {
254 | if err == io.EOF {
255 | break
256 | }
257 | return err
258 | }
259 |
260 | if jm.Aux != nil {
261 | if auxCallback != nil {
262 | auxCallback(jm.Aux)
263 | }
264 | continue
265 | }
266 |
267 | if jm.Progress != nil {
268 | jm.Progress.terminalFd = terminalFd
269 | }
270 | if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
271 | line, ok := ids[jm.ID]
272 | if !ok {
273 | // NOTE: This approach of using len(id) to
274 | // figure out the number of lines of history
275 | // only works as long as we clear the history
276 | // when we output something that's not
277 | // accounted for in the map, such as a line
278 | // with no ID.
279 | line = len(ids)
280 | ids[jm.ID] = line
281 | if termInfo != nil {
282 | fmt.Fprintf(out, "\n")
283 | }
284 | }
285 | diff = len(ids) - line
286 | if termInfo != nil {
287 | cursorUp(out, termInfo, diff)
288 | }
289 | } else {
290 | // When outputting something that isn't progress
291 | // output, clear the history of previous lines. We
292 | // don't want progress entries from some previous
293 | // operation to be updated (for example, pull -a
294 | // with multiple tags).
295 | ids = make(map[string]int)
296 | }
297 | err := jm.Display(out, termInfo)
298 | if jm.ID != "" && termInfo != nil {
299 | cursorDown(out, termInfo, diff)
300 | }
301 | if err != nil {
302 | return err
303 | }
304 | }
305 | return nil
306 | }
307 |
308 | type stream interface {
309 | io.Writer
310 | FD() uintptr
311 | IsTerminal() bool
312 | }
313 |
314 | // DisplayJSONMessagesToStream prints json messages to the output stream
315 | func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
316 | return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
317 | }
318 |
--------------------------------------------------------------------------------
/pkg/util/resize.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // copied from github.com/kubernetes/kubernetes/pkg/kubectl/cmd/util/resize.go
18 | package term
19 |
20 | import (
21 | "fmt"
22 |
23 | "github.com/docker/docker/pkg/term"
24 | "k8s.io/apimachinery/pkg/util/runtime"
25 | "k8s.io/client-go/tools/remotecommand"
26 | )
27 |
28 | // GetSize returns the current size of the user's terminal. If it isn't a terminal,
29 | // nil is returned.
30 | func (t TTY) GetSize() *remotecommand.TerminalSize {
31 | outFd, isTerminal := term.GetFdInfo(t.Out)
32 | if !isTerminal {
33 | return nil
34 | }
35 | return GetSize(outFd)
36 | }
37 |
38 | // GetSize returns the current size of the terminal associated with fd.
39 | func GetSize(fd uintptr) *remotecommand.TerminalSize {
40 | winsize, err := term.GetWinsize(fd)
41 | if err != nil {
42 | runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
43 | return nil
44 | }
45 |
46 | return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height}
47 | }
48 |
49 | // MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with
50 | // initialSizes, or nil if there's no TTY present.
51 | func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue {
52 | outFd, isTerminal := term.GetFdInfo(t.Out)
53 | if !isTerminal {
54 | return nil
55 | }
56 |
57 | t.sizeQueue = &sizeQueue{
58 | t: *t,
59 | // make it buffered so we can send the initial terminal sizes without blocking, prior to starting
60 | // the streaming below
61 | resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)),
62 | stopResizing: make(chan struct{}),
63 | }
64 |
65 | t.sizeQueue.monitorSize(outFd, initialSizes...)
66 |
67 | return t.sizeQueue
68 | }
69 |
70 | // sizeQueue implements remotecommand.TerminalSizeQueue
71 | type sizeQueue struct {
72 | t TTY
73 | // resizeChan receives a Size each time the user's terminal is resized.
74 | resizeChan chan remotecommand.TerminalSize
75 | stopResizing chan struct{}
76 | }
77 |
78 | // make sure sizeQueue implements the resize.TerminalSizeQueue interface
79 | var _ remotecommand.TerminalSizeQueue = &sizeQueue{}
80 |
81 | // monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each
82 | // new event, it sends the current terminal size to resizeChan.
83 | func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) {
84 | // send the initial sizes
85 | for i := range initialSizes {
86 | if initialSizes[i] != nil {
87 | s.resizeChan <- *initialSizes[i]
88 | }
89 | }
90 |
91 | resizeEvents := make(chan remotecommand.TerminalSize, 1)
92 |
93 | monitorResizeEvents(outFd, resizeEvents, s.stopResizing)
94 |
95 | // listen for resize events in the background
96 | go func() {
97 | defer runtime.HandleCrash()
98 |
99 | for {
100 | select {
101 | case size, ok := <-resizeEvents:
102 | if !ok {
103 | return
104 | }
105 |
106 | select {
107 | // try to send the size to resizeChan, but don't block
108 | case s.resizeChan <- size:
109 | // send successful
110 | default:
111 | // unable to send / no-op
112 | }
113 | case <-s.stopResizing:
114 | return
115 | }
116 | }
117 | }()
118 | }
119 |
120 | // Next returns the new terminal size after the terminal has been resized. It returns nil when
121 | // monitoring has been stopped.
122 | func (s *sizeQueue) Next() *remotecommand.TerminalSize {
123 | size, ok := <-s.resizeChan
124 | if !ok {
125 | return nil
126 | }
127 | return &size
128 | }
129 |
130 | // stop stops the background goroutine that is monitoring for terminal resizes.
131 | func (s *sizeQueue) stop() {
132 | close(s.stopResizing)
133 | }
134 |
--------------------------------------------------------------------------------
/pkg/util/resizeevents.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 | // +build !windows
3 |
4 | /*
5 | Copyright 2016 The Kubernetes Authors.
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | */
19 |
20 | package term
21 |
22 | import (
23 | "os"
24 | "os/signal"
25 |
26 | "golang.org/x/sys/unix"
27 | "k8s.io/apimachinery/pkg/util/runtime"
28 | "k8s.io/client-go/tools/remotecommand"
29 | )
30 |
31 | // monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the
32 | // terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send
33 | // it to the resizeEvents channel. The goroutine stops when the stop channel is closed.
34 | func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
35 | go func() {
36 | defer runtime.HandleCrash()
37 |
38 | winch := make(chan os.Signal, 1)
39 | signal.Notify(winch, unix.SIGWINCH)
40 | defer signal.Stop(winch)
41 |
42 | for {
43 | select {
44 | case <-winch:
45 | size := GetSize(fd)
46 | if size == nil {
47 | return
48 | }
49 |
50 | // try to send size
51 | select {
52 | case resizeEvents <- *size:
53 | // success
54 | default:
55 | // not sent
56 | }
57 | case <-stop:
58 | return
59 | }
60 | }
61 | }()
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/util/resizeevents_windows.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package term
18 |
19 | import (
20 | "time"
21 |
22 | "k8s.io/apimachinery/pkg/util/runtime"
23 | "k8s.io/client-go/tools/remotecommand"
24 | )
25 |
26 | // monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send
27 | // it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel
28 | // is closed.
29 | func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
30 | go func() {
31 | defer runtime.HandleCrash()
32 |
33 | size := GetSize(fd)
34 | if size == nil {
35 | return
36 | }
37 | lastSize := *size
38 |
39 | for {
40 | // see if we need to stop running
41 | select {
42 | case <-stop:
43 | return
44 | default:
45 | }
46 |
47 | size := GetSize(fd)
48 | if size == nil {
49 | return
50 | }
51 |
52 | if size.Height != lastSize.Height || size.Width != lastSize.Width {
53 | lastSize.Height = size.Height
54 | lastSize.Width = size.Width
55 | resizeEvents <- *size
56 | }
57 |
58 | // sleep to avoid hot looping
59 | time.Sleep(250 * time.Millisecond)
60 | }
61 | }()
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/util/term.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // copied from github.com/kubernetes/kubernetes/pkg/kubectl/cmd/util/term.go
18 | package term
19 |
20 | import (
21 | "io"
22 | "os"
23 |
24 | "github.com/docker/docker/pkg/term"
25 |
26 | "k8s.io/kubernetes/pkg/util/interrupt"
27 | )
28 |
29 | // SafeFunc is a function to be invoked by TTY.
30 | type SafeFunc func() error
31 |
32 | // TTY helps invoke a function and preserve the state of the terminal, even if the process is
33 | // terminated during execution. It also provides support for terminal resizing for remote command
34 | // execution/attachment.
35 | type TTY struct {
36 | // In is a reader representing stdin. It is a required field.
37 | In io.Reader
38 | // Out is a writer representing stdout. It must be set to support terminal resizing. It is an
39 | // optional field.
40 | Out io.Writer
41 | // Raw is true if the terminal should be set raw.
42 | Raw bool
43 | // TryDev indicates the TTY should try to open /dev/tty if the provided input
44 | // is not a file descriptor.
45 | TryDev bool
46 | // Parent is an optional interrupt handler provided to this function - if provided
47 | // it will be invoked after the terminal state is restored. If it is not provided,
48 | // a signal received during the TTY will result in os.Exit(0) being invoked.
49 | Parent *interrupt.Handler
50 |
51 | // sizeQueue is set after a call to MonitorSize() and is used to monitor SIGWINCH signals when the
52 | // user's terminal resizes.
53 | sizeQueue *sizeQueue
54 | }
55 |
56 | // IsTerminalIn returns true if t.In is a terminal. Does not check /dev/tty
57 | // even if TryDev is set.
58 | func (t TTY) IsTerminalIn() bool {
59 | return IsTerminal(t.In)
60 | }
61 |
62 | // IsTerminalOut returns true if t.Out is a terminal. Does not check /dev/tty
63 | // even if TryDev is set.
64 | func (t TTY) IsTerminalOut() bool {
65 | return IsTerminal(t.Out)
66 | }
67 |
68 | // IsTerminal returns whether the passed object is a terminal or not
69 | func IsTerminal(i interface{}) bool {
70 | _, terminal := term.GetFdInfo(i)
71 | return terminal
72 | }
73 |
74 | // Safe invokes the provided function and will attempt to ensure that when the
75 | // function returns (or a termination signal is sent) that the terminal state
76 | // is reset to the condition it was in prior to the function being invoked. If
77 | // t.Raw is true the terminal will be put into raw mode prior to calling the function.
78 | // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file
79 | // will be opened (if available).
80 | func (t TTY) Safe(fn SafeFunc) error {
81 | inFd, isTerminal := term.GetFdInfo(t.In)
82 |
83 | if !isTerminal && t.TryDev {
84 | if f, err := os.Open("/dev/tty"); err == nil {
85 | defer f.Close()
86 | inFd = f.Fd()
87 | isTerminal = term.IsTerminal(inFd)
88 | }
89 | }
90 | if !isTerminal {
91 | return fn()
92 | }
93 |
94 | var state *term.State
95 | var err error
96 | if t.Raw {
97 | state, err = term.MakeRaw(inFd)
98 | } else {
99 | state, err = term.SaveState(inFd)
100 | }
101 | if err != nil {
102 | return err
103 | }
104 | return interrupt.Chain(t.Parent, func() {
105 | if t.sizeQueue != nil {
106 | t.sizeQueue.stop()
107 | }
108 |
109 | term.RestoreTerminal(inFd, state)
110 | }).Run(fn)
111 | }
112 |
--------------------------------------------------------------------------------
/scripts/docker_push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
3 | docker push jamesgrantmediakind/debug-agent:latest
--------------------------------------------------------------------------------
/scripts/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -x
3 |
4 | # Cleanup
5 | /usr/bin/nsenter -m/proc/1/ns/mnt -- fusermount -u /var/lib/lxc/lxcfs 2> /dev/null || true
6 | /usr/bin/nsenter -m/proc/1/ns/mnt -- [ -L /etc/mtab ] || \
7 | sed -i "/^lxcfs \/var\/lib\/lxc\/lxcfs fuse.lxcfs/d" /etc/mtab
8 |
9 | # Prepare
10 | /usr/bin/nsenter -m/proc/1/ns/mnt -- mkdir -p /var/lib/lxc/lxcfs
11 |
12 | # Mount
13 | LXCFS_USR=/usr/bin/lxcfs
14 | LXCFS=/usr/local/bin/lxcfs
15 | /usr/bin/nsenter -m/proc/1/ns/mnt -- [ -f $LXCFS_USR ] && LXCFS=$LXCFS_USR
16 | exec /usr/bin/nsenter -m/proc/1/ns/mnt -- $LXCFS -p "/run/lxcfs-$$.pid" /var/lib/lxc/lxcfs/ &
17 |
18 | if grep -q io.containerd.runtime.v1.linux /proc/$PPID/cmdline
19 | then
20 | export KCTLDBG_CONTAINERDV1_SHIM=io.containerd.runc.v1
21 | fi
22 |
23 | /bin/debug-agent "$@"
24 |
--------------------------------------------------------------------------------
/version.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | if [[ -n ${GIT_COMMIT-} ]] || GIT_COMMIT=$(git rev-parse "HEAD^{commit}" 2>/dev/null); then
5 | if [[ -z ${GIT_TREE_STATE-} ]]; then
6 | # Check if the tree is dirty. default to dirty
7 | if git_status=$(git status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then
8 | GIT_TREE_STATE="clean"
9 | else
10 | GIT_TREE_STATE="dirty"
11 | fi
12 | fi
13 |
14 | # Use git describe to find the version based on tags.
15 | if [[ -n ${GIT_VERSION-} ]] || GIT_VERSION=$(git describe --tags --abbrev=14 "${GIT_COMMIT}^{commit}" 2>/dev/null); then
16 | # This translates the "git describe" to an actual semver.org
17 | # compatible semantic version that looks something like this:
18 | # v1.0.0-beta.0.10+4c183422345d8f
19 | #
20 | # downstream consumers are expecting it there.
21 | DASHES_IN_VERSION=$(echo "${GIT_VERSION}" | sed "s/[^-]//g")
22 | if [[ "${DASHES_IN_VERSION}" == "---" ]] ; then
23 | # We have distance to subversion (v1.1.0-subversion-1-gCommitHash)
24 | GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-\([0-9]\{1,\}\)-g\([0-9a-f]\{14\}\)$/.\1\+\2/")
25 | elif [[ "${DASHES_IN_VERSION}" == "--" ]] ; then
26 | # We have distance to base tag (v1.1.0-1-gCommitHash)
27 | GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-g\([0-9a-f]\{14\}\)$/+\1/")
28 | fi
29 | if [[ "${GIT_TREE_STATE}" == "dirty" ]]; then
30 | # git describe --dirty only considers changes to existing files, but
31 | # that is problematic since new untracked .go files affect the build,
32 | # so use our idea of "dirty" from git status instead.
33 | GIT_VERSION+="-dirty"
34 | fi
35 |
36 |
37 | # If GIT_VERSION is not a valid Semantic Version, then refuse to build.
38 | if ! [[ "${GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
39 | echo "GIT_VERSION should be a valid Semantic Version. Current value: ${GIT_VERSION}"
40 | echo "Please see more details here: https://semver.org"
41 | exit 1
42 | fi
43 | fi
44 | fi
45 |
46 | echo "-X 'github.com/jamestgrant/kubectl-debug/version.gitVersion=${GIT_VERSION}'"
47 |
--------------------------------------------------------------------------------
/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | var (
4 | gitVersion = "v1.0.0-master+$Format:%h$"
5 | )
6 |
7 | func Version() string {
8 | return gitVersion
9 | }
10 |
--------------------------------------------------------------------------------