关于云计算:Kubernetes-上调试-distroless-容器

6次阅读

共计 6081 个字符,预计需要花费 16 分钟才能阅读完成。

TL;DR

本文内容:

  • 介绍 distroless 镜像、作用以及简略的应用
  • 如何针对 distroless 容器的进行调试
  • 长期容器 (v.1.18+) 的应用

Distroless 镜像

Distroless 容器,顾名思义应用 Distroless 镜像作为根底镜像运行的容器。

“Distroless” 镜像只蕴含了你的应用程序以及其运行时所须要的依赖。不蕴含你能在规范 Linxu 发行版里的能够找到的包管理器、shells 或者其余程序。

GoogleContainerTools/distroless 针对不同语言提供了 distroless 镜像:

  • gcr.io/distroless/static-debian11
  • gcr.io/distroless/base-debian11
  • gcr.io/distroless/java-debian11
  • gcr.io/distroless/cc-debian11
  • gcr.io/distroless/nodejs-debian11
  • gcr.io/distroless/python3-debian11

Distroless 镜像有什么用?

那些可能是构建镜像时须要的,但大部分并不是运行时须要的。这也是为什么上篇文章介绍 Buildpacks 时说的一个 builder 的 stack 镜像蕴含构建时根底镜像和运行时根底镜像,这样能够做到镜像的最小化。

其实管制体积并不是 distroless 镜像的次要作用。将运行时容器中的内容限度为应用程序所需的依赖,此外不应该装置任何货色。这种形式可能极大的晋升容器的安全性,也是 distroless 镜像的最重要作用。

这里并不会再深刻探索 distroless 镜像,而是如何调试 distroless 容器

没有了包管理器,镜像构建实现后就不能再应用相似 aptyum 的包管理工具;没有了 shell,容器运行后无奈再进入容器。

“就像一个没有任何门的房间,也无奈装置门。” Distroless 镜像在晋升容器安全性的同时,也为调试减少了难度。

应用 distroless 镜像

写个很简略的 golang 利用:

package main

import (
    "fmt"
    "net/http"
)

func defaultHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello world!")
}

func main() {http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":8080", nil)
}

比方应用 gcr.io/distroless/base-debian11 作为 golang 利用的根底镜像:

FROM golang:1.12 as build-env

WORKDIR /go/src/app
COPY . /go/src/app

RUN go get -d -v ./...

RUN go build -o /go/bin/app

FROM gcr.io/distroless/base-debian11
COPY --from=build-env /go/bin/app /
CMD ["/app"]

应用镜像创立 deployment

$ kubectl create deploy golang-distroless --image addozhang/golang-distroless-example:latest

$ kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
golang-distroless-784bb4875-srmmr   1/1     Running   0          3m2s

尝试进入容器:

$ kubectl exec -it golang-distroless-784bb4875-srmmr -- sh
error: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "b76e800eafa85d39f909f39fcee4a4ba9fc2f37d5f674aa6620690b8e2939203": OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "sh": executable file not found in $PATH: unknown

如何调试 Distroless 容器

1. 应用 distroless debug 镜像

GoogleContainerTools 为每个 distroless 镜像都提供了 debug tag,适宜在开发阶段进行调试。如何应用?替换容器的 base 镜像:

FROM golang:1.12 as build-env

WORKDIR /go/src/app
COPY . /go/src/app

RUN go get -d -v ./...

RUN go build -o /go/bin/app

FROM gcr.io/distroless/base-debian11:debug # use debug tag here
COPY --from=build-env /go/bin/app /
CMD ["/app"]

从新构建镜像并部署,得益于 debug 镜像中提供了 busybox shell 让咱们能够 exec 到容器中。

2. debug 容器与共享过程命名空间

同一个 pod 中能够运行多个容器,通过设置 pod.spec.shareProcessNamespacetrue,来让同一个 Pod 中的多容器共享同一个过程命名空间。

Share a single process namespace between all of the containers in a pod.

 When this is set containers will be able to view and signal processes from
 other containers in the same pod, and the first process in each container
 will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both
 be set. Optional: Default to false.

增加一个应用 ubuntu 镜像的 debug 容器,这里为了测试(前面解释)咱们为原容器增加 securityContext.runAsUser: 1000,模仿两个容器应用不同的 UID 运行:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: golang-distroless
  name: golang-distroless
spec:
  replicas: 1
  selector:
    matchLabels:
      app: golang-distroless
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: golang-distroless
    spec:
      shareProcessNamespace: true
      containers:
      - image: addozhang/golang-distroless-example:latest
        name: golang-distroless-example
        securityContext:
          runAsUser: 1000
        resources: {}
      - image: ubuntu
        name: debug
        args: ['sleep', '1d']
        securityContext:
          capabilities:
            add:
            - SYS_PTRACE
        resources: {}
status: {}

更新 deployment 之后:

$ kubectl get po
NAME                                 READY   STATUS    RESTARTS   AGE
golang-distroless-85c4896c45-rkjwn   2/2     Running   0          3m12s

$ kubectl get po -o json | jq -r '.items[].spec.containers[].name'
golang-distroless-example
debug

而后通过 debug 容器来进入到 pod 中:

$ kubectl exec -it golang-distroless-85c4896c45-rkjwn -c debug -- sh

而后在容器中执行:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:54 ?        00:00:00 /pause                      # infra 容器
1000         7     0  0 14:54 ?        00:00:00 /app                         # 原容器,UID 为 1000
root        19     0  0 14:55 ?        00:00:00 sleep 1d                 # debug 容器
root        25     0  0 14:55 pts/0    00:00:00 sh
root        32    25  0 14:55 pts/0    00:00:00 ps -ef

尝试拜访 过程 7 的过程空间:

$ cat /proc/7/environ
$ cat: /proc/7/environ: Permission denied

咱们须要为 debug 容器加上:

securityContext:
  capabilities:
    add:
    - SYS_PTRACE

之后再拜访就失常了:

$ cat /proc/7/environ
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=golang-distroless-58b6c5f455-v9zkvSSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crtKUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_PORT_443_TCP_PORT=443KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1KUBERNETES_SERVICE_HOST=10.43.0.1KUBERNETES_SERVICE_PORT=443KUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_PORT=tcp://10.43.0.1:443HOME=/root

同样咱们也能够拜访过程的文件系统:

$ cd /proc/7/root
$ ls
app  bin  boot  dev  etc  home  lib  lib64  proc  root  run  sbin  sys  tmp  usr  var

无需批改容器的根底镜像,应用 pod.spec.shareProcessNamespace: true 配合平安配置中减少 SYS_PTRACE 个性,为 debug 容器赋予残缺的 shell 拜访来调试利用。然而批改 YAML 和平安配置只适宜在测试环境应用,到了生产环境这些都是不容许的。

咱们就须要用到 kubectl debug 了。

3. Kubectl debug

针对不同的资源 kubectl debug 能够进行不同操作:

  • 负载:创立一个正在运行的 Pod 的拷贝,并能够批改局部属性。比方在拷贝中应用新版本的 tag。
  • 负载:为运行中的 Pod 减少一个长期容器(上面介绍),应用长期容器中的工具调试,无需重启 Pod。
  • 节点:在节点上创立一个 Pod 运行在 节点的 host 命名空间,能够拜访节点的文件系统。

3.1 长期容器

从 Kubernetes 1.18 之后开始,能够应用 kubectl 为运行的 pod 增加一个长期容器。这个命令还处于 alpha 阶段,因而须要在“feature gate”中关上。

在应用 k3d 创立 k3s 集群时,关上 EphemeralContainers feature:

$ k3d cluster create test --k3s-arg "--kube-apiserver-arg=feature-gates=EphemeralContainers=true"@

而后创立长期容器,创立实现后会间接进入容器:

$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent
#长期容器 shell
$ apt update && apt install -y curl
$ curl localhost:8080
Hello world!

值得注意的是,长期容器无奈与原容器共享过程命名空间:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 02:59 pts/0    00:00:00 bash
root      3042     1  0 03:02 pts/0    00:00:00 ps -ef

能够通过增加参数 --target=[container] 来将长期容器挂接到指标容器。这里与 pod.spec.shareProcessNamespace 并不同,过程号为 1 的过程是指标容器的过程,而后者的过程是 infra 容器的过程 /pause

$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent --target=golang-distroless-example

留神:目前的版本还不反对删除长期容器,参考 issue,反对的版本:

3.2 拷贝 Pod 并增加容器

除了增加长期容器以外,另一种形式就是创立一个 Pod 的拷贝,并增加一个容器。留神这里的是一般容器,不是长期容器。 留神这里加上了 --share-processes

$ kubectl debug golang-distroless-85c4896c45-rkjwn -it --image=ubuntu --image-pull-policy=IfNotPresent --share-processes --copy-to=golang-distroless-debug

留神这里加上了 --share-processes,会主动加上 pod.spec.shareProcessNamespace=true

$ kubectl get po golang-distroless-debug -o jsonpath='{.spec.shareProcessNamespace}'
true

留神:应用 kubectl debug 调试,并不能为 pod 主动加上 SYS_PTRACE 平安个性,这就意味着如果容器应用的 UID 不统一,就无法访问过程空间。 截止发文,打算在 1.23 中反对。

总结

目前下面所有的都不适宜在生产环境应用,无奈在不批改 Pod 定义的状况下进行调试。

冀望 Kubernetes 1.23 版本之后 debug 性能增加 SYS_PTRACE 的反对。到时候,再尝试一下。

文章对立公布在公众号 云原生指北

正文完
 0