[TOC]

DIND(Docker-in-Docker)是让你可以在 Docker 容器里面运行 Docker 的一种方式,在 Docker 6.0 中实现的方式是,为容器添加特权模式。如果你想把 Docker 本身作为一项服务提供给 Docker 容器,这个工具很有用。比如说,如果你想试用某种自动化工具或方法。请注意,Docker 的 “内部”实例是最新的 Docker二进制代码,构建时可以从 docker.io 来获取。另外牢记一点:以这种方式运行的实例是在特权模式下运行的;正因为如此,你将它们暴露在非 Docker 化的外界面前时,需要采取更多的防范措施。

dind一般分两种方式:

1
2
一种是使用宿主机的docker.sock,通过docker run -v /var/run/docker.sock:/var/run/docker.sock,将宿主机sock引入到容器内。这样当容器内使用docker命令时,实际上调用的是宿主机的docker daemon,这种方式速度快,但是安全性不够。
另一种是启动一个docker:dind容器a,再启动一个docker容器b,容器b指定host为a容器内的docker daemon;

在Kubernetes上运行的Docker构建(无论是使用JenkinsX,Tekton还是其他),您将需要Docker守护进程,该守护进程可以在Docker(DinD)中使用Docker进行部署,DinD是在Docker容器中运行的Docker守护进程。
至于构建本身,您将需要一个连接到DIND socket的pod(容器)来运行docker build命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: v1
kind: Pod
metadata:
name: docker-build
spec:
containers:
- name: dind # Docker in Docker container
image: docker:19.03.3-dind
securityContext:
privileged: true
env:
- name: DOCKER_TLS_CERTDIR
value: ''
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
- name: docker # Builder container
image: docker:19.03.3-git
securityContext:
privileged: true
command: ['cat']
tty: true
env:
- name: DOCKER_BUILDKIT
value: '1'
- name: DOCKER_HOST
value: tcp://localhost:2375
volumes:
- name: dind-storage
emptyDir: {}
- name: docker-socket-volume
hostPath:
path: /var/run/docker.sock
type: File

上面的容器由2个容器组成—一个用于DinD,一个用于镜像构建。要使用构建容器运行构建,可以访问其shell,克隆一些存储库并运行构建流程:

~ $ kubectl exec –stdin –tty docker-build – /bin/sh # Open shell session
~ # git clone https://github.com/username/reponame.git # Clone some repository
~ # cd reponame
~ # docker build –build-arg BUILDKIT_INLINE_CACHE=1 -t name:tag –cache-from username/reponame:latest .

=> importing cache manifest from martinheinz/python-project-blueprint:flask

=> => writing image sha256:…
=> => naming to docker.io/library/name:tag
=> exporting cache
=> => preparing build cache for export

最终docker build使用了一些新选项—–cache-from image:tag,来告诉Docker它应该使用(远程)仓库中的指定镜像作为缓存源。这样,即使缓存的层未存储在本地文件系统中,我们也可以利用缓存的优点。
另一个选项—-build-arg BUILDKIT_INLINE_CACHE=1用于在创建缓存元数据时将其写入镜像。这必须用于–cache-from工作,有关更多信息,请参阅文档(https://docs.docker.com/engine/reference/commandline/build/#specifying-external-cache-sources)。

Kubernetes之修改NodePort对外映射端口范围

k8s默认使用NodePort对外映射端口范围是30000-50000可以通过修改kube-apiserver参数修改端口范围
修改配置文件/opt/kubernetes/cfg/kube-apiserver增加或者修改以下配置 –service-node-port-range=30000-34000

1
2
3
# 样式命令一步到位
sed -i "/image:/i\ - --service-node-port-range=30000-34000" /etc/kubernetes/manifests/kube-apiserver.yaml
systemctl restart kubelet

K8s

k8s中删除一个pod时卡住问题
再删除pod的时候,卡在了终端,一直terminating状态。
可执行以下命令强制删除

删除POD

kubectl delete pod [pod name] –force –grace-period=0 -n [namespace]

删除NAMESPACE

kubectl delete namespace NAMESPACENAME –force –grace-period=0

在Kubernetes中提供了grace-period,在Pod删除时此选项会起作用,会延迟一定时长才进行删除,缺省未设定的情况下会等待30s中之后删除。

这个问题明显是Docker ImageGC 的问题。应该是内存不够了。导致的kubelet挂掉了。但是查看df -h 。磁盘空间还是够的。

Kubernetes 常规排错指南

0.节点kubelet相关信息

1
2
3
4
5
6
7
8
9
10
11
# 查看 kubelet 运行状态及其日志有无错误信息提示。
systemctl status kubelet
journalctl -xefu kubelet

# 查看 Conatinerd 运行状态及其日志有无错误信息提示。
systemctl status containerd
journalctl -xefu containerd

# 查看 docker 运行状态及其日志有无错误信息提示。
systemctl status docker
journalctl -xefu docker


1.节点状态及其相关信息

1
2
3
4
5
6
7
8
9
10
11
# 1.查看集群节点是否处于Ready状态。
kubectl get nodes -o wide |grep NotReady

# 2.如果是Ready状态请,查看节点描述信息以及资源信息。
kubectl describe nodes work-226

# 3.如果是NotReady状态,请查看对应节点上docker、containerd以及kubernetes相关。
$ ps aux|grep kube-proxy
$ ps aux|grep kubelet
$ ps aux|grep docker
$ ps aux|grep containerd


2.查看集群关键组件的状态是否是Healthy

1
2
3
4
5
6
~$ kubectl get cs
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health":"true","reason":""}

如果状态都是Healthy的说明集群是正常,如果是Unhealthy状态, 则执行查询node对应的节点是否存在问题,如果存在需要登陆到对应节点上。


3.查看系统组件或者应用Pod状态及其日志

1
2
3
4
5
6
7
8
# 1.排查不是Running状态的Pod。
kubectl get pod -n kube-system | grep -v "Running"

# 2.排查Pod非Running状态的原因
kubectl describe pod -n kube-system kube-proxy-6jsmp

# 3.查看Pod日志信息
kubectl logs -f --tail 50 -n kube-system kube-proxy-6jsmp

10 个 Kubernetes 安全上下文配置

https://blog.csdn.net/qq_34556414/article/details/118683892

在 Kubernetes 中安全地运行工作负载是很困难的,有很多配置都可能会影响到整个 Kubernetes API 的安全性,这需要我们有大量的知识积累来正确的实施。Kubernetes 在安全方面提供了一个强大的工具 securityContext,每个 Pod 和容器清单都可以使用这个属性。在本文中我们将了解各种 securityContext 的配置,探讨它们的含义,以及我们应该如何使用它们。

securityContext 设置在 PodSpec 和ContainerSpec 规范中都有定义,这里我们分别用[P]和[C]来表示。需要注意的是,如果一个设置在两个作用域中都可以使用和配置,那么我们应该优先考虑设置容器级别的。

1runAsNonRoot [P/C]

我们知道容器是使用 namespaces 和 cgroups 来限制其进程,但只要在部署的时候做了一次错误的配置,就可以让这些进程访问主机上的资源。如果该进程以 root 身份运行,它对这些资源的访问权限与主机 root 账户是相同的。此外,如果其他 pod 或容器设置被用来减少约束(比如 procMount 或 capabilities),拥有一个 root UID 就会提高风险,除非你有一个非常好的原因,否则你不应该以 root 身份运行一个容器。

那么,如果你有一个使用 root 的镜像需要部署,那应该怎么办呢?

1.1 使用基础镜像中提供的用户

通常情况下,基础镜像已经创建并提供了一个用户,例如,官方的 Node.js 镜像带有一个 UID 为 1000 的名为 node 的用户,我们就可以使用该身份来运行容器,但他们并没有在 Dockerfile 中明确地设置当前用户。我们可以在运行时用 runAsUser 设置来配置它,或者用自定义的 Dockerfile 来更改镜像中的当前用户。这里我们来看看使用自定义的 Dockerfile 来构建我们自己的镜像的例子。

在不深入了解镜像构建的情况下,让我们假设我们有一个预先构建好的 npm 应用程序。这里是一个最小的 Dockerfile 文件,用来构建一个基于 node:slim 的镜像,并以提供的 node 用户身份运行。

FROM node:slim
COPY --chown=node . /home/node/app/   # <--- Copy app into the home directory with right ownership
USER 1000                             # <--- Switch active user to “node” (by UID)
WORKDIR /home/node/app                # <--- Switch current directory to app
ENTRYPOINT ["npm", "start"]           # <--- This will now exec as the “node” user instead of root

其中以 USER 开头的一行就是关键设置,这使得 node 成为从这个镜像启动的任何容器里面的默认用户。我们使用 UID 而不是用户的名字,因为 Kubernetes 无法在启动容器前将镜像的默认用户名映射到 UID 上,并且在部署时指定 runAsNotRoot: true,会返回有关错误。

1.2 基础镜像没有提供用户

如果我们使用的基础镜像没有提供一个可以使用的用户,那么我们又应该怎么做呢?对于大部分进程来说,我们只需在自定义的 Dockerfile 中创建一个用户并使用它即可。如下所示:

FROM node:slim
RUN useradd somebody -u 10001 --create-home --user-group  # <--- Create a user
COPY --chown=somebody . /home/somebody/app/
USER 10001
WORKDIR /home/somebody/app
ENTRYPOINT ["npm", "start"]

这里我们增加了一行创建用户的 RUN 命令即可。不过需要注意的是这对于 node.js 和 npm 来说,这很好用,但是其他工具可能需要文件系统的不同元素进行所有权变更。如果遇到任何问题,需要查阅对应工具的文档。
2runAsUser/runAsGroup [P/C]

容器镜像可能有一个特定的用户或组,我们可以用 runAsUser 和 runAsGroup 来进行覆盖。通常,这些设置与包含具有相同所有权 ID 的文件的卷挂载结合在一起。

....
spec:
  containers:
    - name: web
      image: mycorp/webapp:1.2.3
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
....

不过使用这些配置也是有风险的,因为你为容器做出的运行时决定可能与原始镜像不兼容。例如,jenkins/jenkins 镜像以名为 jenkins:jenkins 的组:用户身份运行,其应用文件全部由该用户拥有。如果我们配置一个不同的用户,它将无法启动,因为该用户不存在于镜像的 /etc/passwd 文件中。即使它以某种方式存在,它也很可能在读写 jenkins:jenkins 拥有的文件时出现问题。我们可以用一个简单的 docker 运行命令来验证这个问题。

$ docker run --rm -it -u eric:eric jenkins/jenkins
docker: Error response from daemon: unable to find user eric: no matching entries in passwd file.

上面我们提到确保容器进程不以 root 用户身份运行是一个非常好的主意,但不要依赖 runAsUser 或 runAsGroup 设置来保证这一点,未来有人可能会删除这些配置,请确保同时将 runAsNonRoot 设置为 true。

5避免使用特权容器 [C]

给容器授予特权模式是非常危险的,一般会有一种更简单的方式来实现特定的权限,或者可以通过授予 Linux Capabilities 权限来控制。容器运行时控制器着特权模式的具体实现,但是它会授予容器所有的特权,并解除由 cgroup 控制器执行的限制,它还可以修改 Linux 安全模块的配置,并允许容器内的进程逃离容器。

容器在宿主机中提供了进程隔离,所以即使容器是使用 root 身份运行的,也有容器运行时不授予容器的 Capabilities。如果配置了特权模式,容器运行时就会授予系统 root 的所有能力,从安全角度来看,这是很危险的,因为它允许对底层宿主机系统的所有操作访问。

避免使用特权模式,如果你的容器确实需要额外的能力,只需通过添加 capabilities 来满足你的需求。除非你的容器需要控制主机内核中的系统级设置,如访问特定的硬件或重新配置网络,并且需要访问主机文件系统,那么它就不需要特权模式。

securityContext:
  allowPrivilegeEscalation: true
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE
  # www-data -> 101
  runAsUser: 101

6Linux Capabilities [C]

Capabilities 是一个内核级别的权限,它允许对内核调用权限进行更细粒度的控制,而不是简单地以 root 身份运行。Capabilities 包括更改文件权限、控制网络子系统和执行系统管理等功能。在 securityContext 中,Kubernetes 可以添加或删除 Capabilities,单个 Capabilities 或逗号分隔的列表可以作为一个字符串数组进行配置。另外,我们也可以使用 all 来添加或删除所有的配置。这种配置会被传递给容器运行时,在它创建容器的时候会配置上 Capabilities 集合,如果 securityContext 中没有配置,那么容器将会直接容器运行时提供的所有默认配置。

securityContext:
  capabilities:
    drop:
      - all
    add: ["MKNOD"]

一般推荐的做法是先删除所有的配置,然后只添加你的应用程序实际需要的,在大部分情况下,应用程序在正常运行中实际上不需要任何 Capabilities,通过删除所有配置来测试,并通过监控审计日志来调试问题,看看哪些功能被阻止了。

请注意,当在 securityContext 中列出要放弃或添加的 Capabilities 时,你要删除内核在命名 Capabilities 时使用的 CAP_ 前缀。capsh 工具可以给我们一个比较友好的调试信息,可以来说明你的容器中到底启用了哪些 Capabilities,当然不要在生产容器中使用这个工具,因为这使得攻击者很容易弄清楚哪些 Capabilities 被启用了。