Linux通过curl访问docker.sock/API执行docker操作

来自三线的随记

官方API doc: https://docs.docker.com/engine/api/v1.41/

官方API doc(with example): https://docs.docker.com/engine/api/sdk/examples/

不同版本的docker的api doc: https://docs.docker.com/engine/api/#api-version-matrix

tips: Linux下对socket进行抓包

通过curl socket完成镜像拉取操作

通过docker cli进行操作+抓包获取基本报文格式

POST /v1.41/images/create?fromImage=10.82.123.123%2Fpublic%2Fmaven&tag=3.3.9-public HTTP/1.1
Host: docker
User-Agent: Docker-Client/20.10.17 (linux)
Content-Length: 0
Content-Type: text/plain

命令实现

curl --unix-socket /var/run/docker.sock "http://localhost/v1.41/images/create?fromImage=10.82.123.123%2Fpublic%2Fmaven&tag=3.3.9-public" -X POST

如果拉取镜像需要认证可以加上http header

X-Registry-Auth: eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJwYXNzd29yZCIsInNlcnZlcmFkZHJlc3MiOiIxMC44Mi4xMjMuMTIzIn0=

通过curl socket获取镜像dangling镜像信息

curl -s --unix-socket /var/run/docker.sock "http://localhost/v1.41/images/json?filters=\{\"dangling\":\[\"true\"\]\}"

通过curl socket获取指定REPOSITORY+TAG镜像信息

注意是全名字匹配

curl -s --unix-socket /var/run/docker.sock "http:/v1.24/images/json?filters=\{\"reference\":\{\"mysql\":true\}\}"

[{"Containers":-1,"Created":1602576181,"Id":"sha256:8e85dd5c32558ea5c22cc4786cff512c1940270a50e7dbc21ad2df42f0637de4","Labels":null,"ParentId":"","RepoDigests":["mysql@sha256:86b7c83e24c824163927db1016d5ab153a9a04358951be8b236171286e3289a4"],"RepoTags":["mysql:8.0.21"],"SharedSize":-1,"Size":544193587,"VirtualSize":544193587},{"Containers":-1,"Created":1532654095,"Id":"sha256:6bb891430fb6e2d3b4db41fd1f7ece08c5fc769d8f4823ec33c7c7ba99679213","Labels":null,"ParentId":"","RepoDigests":["mysql@sha256:aaba540cdd9313645d892f4f20573e8b42b30e5be71c054b7befed2f7da5f85b"],"RepoTags":["mysql:5.7.22"],"SharedSize":-1,"Size":371941626,"VirtualSize":371941626}]

如果需要半匹配,用 * 号即可(测试下来发现好像只能省略后缀,不能省略前缀,可以匹配tag,区分大小写)

curl -s --unix-socket /var/run/docker.sock "http:/v1.24/images/json?filters=\{\"reference\":\{\"*m*\":true\}\}"|jq .

[
  {
    "Containers": -1,
    "Created": 1665115518,
    "Id": "sha256:9622db9aed586c97c6a18d912b965f3d4c02fdf749650c7ee57a9a6b8cde3dd7",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": null,
    "RepoTags": [
      "sonarqube:8.9.9-community"
    ],
    "SharedSize": -1,
    "Size": 499466355,
    "VirtualSize": 499466355
  },
  {
    "Containers": -1,
    "Created": 1612947183,
    "Id": "sha256:8415759abc07b3d3dcd43cecd924caa3ed68bc5d4a4166e50565a4a1b344a7e5",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": null,
    "RepoTags": [
      "node:14.15.5-stretch-slim"
    ],
    "SharedSize": -1,
    "Size": 166658514,
    "VirtualSize": 166658514
  },
  {
    "Containers": -1,
    "Created": 1602576181,
    "Id": "sha256:8e85dd5c32558ea5c22cc4786cff512c1940270a50e7dbc21ad2df42f0637de4",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": [
      "mysql@sha256:86b7c83e24c824163927db1016d5ab153a9a04358951be8b236171286e3289a4"
    ],
    "RepoTags": [
      "mysql:8.0.21"
    ],
    "SharedSize": -1,
    "Size": 544193587,
    "VirtualSize": 544193587
  },
  {
    "Containers": -1,
    "Created": 1532654095,
    "Id": "sha256:6bb891430fb6e2d3b4db41fd1f7ece08c5fc769d8f4823ec33c7c7ba99679213",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": [
      "mysql@sha256:aaba540cdd9313645d892f4f20573e8b42b30e5be71c054b7befed2f7da5f85b"
    ],
    "RepoTags": [
      "mysql:5.7.22"
    ],
    "SharedSize": -1,
    "Size": 371941626,
    "VirtualSize": 371941626
  }
]

简单的通过curl + socket进行清理镜像的bash脚本例子

#!/usr/bin/env bash
#exec 1>/proc/1/fd/1 2>/proc/1/fd/2
docker_api_version=v1.41
curl_options=(
  "-s"
  "--unix-socket" "/var/run/docker.sock"
)
dangling_images=(`curl "${curl_options[@]}" "http://localhost/${docker_api_version}/images/json?filters=%7B%22dangling%22%3A%5B%22true%22%5D%7D&all=1" | jq -r .[].Id`)
# TODO: clean up stopped non-k8s containers
echo "cleaning dangling images:"
for image in ${dangling_images[*]}
do
  echo $image
  (set -x; curl -X DELETE "${curl_options[@]}" "http://localhost/${docker_api_version}/images/$image" )
done

echo -n "cleaning images without specific string: "
echo ${image_exclude:="(/kube-system/)|(/library/)"}
clean_images=(`curl "${curl_options[@]}" "http://localhost/${docker_api_version}/images/json"|jq -r ".[] | select(.RepoTags != null )| .RepoTags[]"|grep -v \<none|grep -Ev "${image_exclude}"`)
for image in ${clean_images[*]}
do
  echo $image
  (set -x; curl -X DELETE "${curl_options[@]}" "http://localhost/${docker_api_version}/images/$image" )
done
echo "End of docker images clean"

curl socket获取Exited容器名字

curl -s --unix-socket /var/run/docker.sock "http://localhost/v1.41/containers/json?filters=\{\"status\":\[\"exited\"\]\}" | jq -r ".[].Names[]" | cut -c 2-

curl socket删除容器

curl -X DELETE -s --unix-socket /var/run/docker.sock "http://localhost/v1.41/containers/$container"

利用daemonset部署curl + socket进行清理镜像的定时任务到k8s集群中

${your_image} 自行替换

依赖工具 curl + jq + crontab

改不同的发行版crontab需要自行适配cron的启动命令

例如Debian系容器镜像可以通过下列方式进行构建依赖的容器镜像

#debian_install_package="curl wget iputils-ping iproute2 dnsutils net-tools vim telnet netcat procps lsof tcpdump cron jq" #常见依赖工具

debian_install_package="curl cron jq"
sed -i -e "s/security.debian.org/mirrors.ustc.edu.cn/" -e "s/deb.debian.org/mirrors.ustc.edu.cn/" /etc/apt/sources.list && \
apt-get update && \
apt-get install --no-install-recommends -y ${debian_install_package} && \
apt-get autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*  && \
touch /var/spool/cron/crontabs/root  && \
chmod -v 600 /var/spool/cron/crontabs/root  && \
chgrp -v crontab /var/spool/cron/crontabs/root

操作/var/spool/cron/crontabs/root文件只是为了方便通过echo等方式直接操作root用户的crontab

使用microdnf的yum系容器镜像可以通过下列方式进行构建依赖的容器镜像

#microdnf_install_package="curl wget iputils iproute bind-utils net-tools hostname vim telnet nmap-ncat procps lsof tcpdump cronie findutils zip unzip jq" #常见依赖工具

microdnf_install_package="cronie jq"

microdnf install ${microdnf_install_package} && \
rpm -i /tmp/${java_version}-linux-x64.rpm && \
microdnf clean all && \
rm -rvf /tmp/* && \
touch /var/spool/cron/root && \
chmod 600 /var/spool/cron/root

操作/var/spool/cron/root文件同样只是为了方便通过echo等方式直接操作root用户的crontab

注意有的发行版容器镜像需要tty才可以输出详细的crond 前台运行日志,例如某些Alpine (/usr/sbin/crond -f -S /dev/stdout + tty: true)

具体k8s编排

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    k8s-app: docker-image-cleaner
    app: docker-image-cleaner
  name: docker-image-cleaner
  namespace: kube-system
spec:
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      k8s-app: docker-image-cleaner
      app: docker-image-cleaner
  template:
    metadata:
      labels:
        k8s-app: docker-image-cleaner
        app: docker-image-cleaner
    spec:
      containers:
      - image: ${your_image}
        imagePullPolicy: IfNotPresent
        name: docker-image-cleaner
        command:
        - cron
        - -L
        - "8"
        - "-f"
        lifecycle:
          postStart:
            exec:
              command:
               - /bin/sh
               - -c
               - |
                 cat <<\EOF > /clean.sh
                 #!/usr/bin/env bash
                 exec 1>/proc/1/fd/1 2>/proc/1/fd/2
                 docker_api_version=v1.41
                 curl_options=(
                   "-L"
                   "-s"
                   "--unix-socket" "/var/run/docker.sock"
                 )
                 # Remove exited non-k8s containers
                 exited_containers=(`curl "${curl_options[@]}" "http://localhost/${docker_api_version}/containers/json?filters=\{\"status\":\[\"exited\"\]\}" | jq -r ".[].Names[]" | cut -c 2- | grep -v k8s`)
                 echo "removing exited non-k8s containers"
                 for container in ${exited_containers[*]}
                 do
                   echo $container
                   (set -x;curl -X DELETE "${curl_options[@]}" "http://localhost/${docker_api_version}/containers/$container")
                 done

                 echo "cleaning dangling images:"
                 for image in ${dangling_images[*]}
                 do
                   echo $image
                   (set -x; curl -X DELETE "${curl_options[@]}" "http://localhost/${docker_api_version}/images/$image" )
                 done
 
                 echo -n "cleaning images without specific string: "
                 echo ${image_exclude:="(/kube-system/)|(/library/)"}
                 clean_images=(`curl "${curl_options[@]}" "http://localhost/${docker_api_version}/images/json"|jq -r ".[] | select(.RepoTags != null )| .RepoTags[]"|grep -v \<none|grep -Ev "${image_exclude}"`)
                 for image in ${clean_images[*]}
                 do
                   echo $image
                   (set -x; curl -X DELETE "${curl_options[@]}" "http://localhost/${docker_api_version}/images/$image" )
                 done
                 echo "End of image clean"
                 EOF
                 chmod u+x /clean.sh
                 echo "0 2 * * * bash /clean.sh" |crontab -
                 crontab -l > /proc/1/fd/1
        resources:
          limits:
            cpu: 500m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 64Mi
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
          privileged: true
        volumeMounts:
        - mountPath: /var/run/docker.sock
          name: docker-sock
      restartPolicy: Always
      tolerations:
      - effect: NoSchedule
        operator: Exists
      volumes:
      - hostPath:
          path: /var/run/docker.sock
          type: ""
        name: docker-sock
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 60%
    type: RollingUpdate