最近由于 Dockerhub 仓库被封导致常用的镜像无法拉取,好在网上已经有搭建镜像仓库的教程,比如使用 cloudflare-docker-proxy 基于 Cloudflare Workers 搭建镜像代理服务。虽然解决了镜像拉取的问题,但对于部署在 k8s 集群上的应用,管理员需要手动修改 YAML 文件中的镜像地址,如果集群上部署的应用较多这个工作量就会很大。更麻烦的是一些使用 Operator 管理的应用,甚至无法直接修改镜像地址。

image-operator 是基于 Admission Webhook 实现的一个 Operator,工作原理是在 Pod 创建时根据规则重写镜像地址,这个过程对使用者来说是透明的,无需手动修改 YAML 中的镜像地址。

安装 image-operator

image-operator 提供了 Helm Chart 方便安装,首先添加 Helm 仓库:

1
$ helm repo add image-operator https://yxwuxuanl.github.io/k8s-image-operator/

接着安装 image-operator

1
$ helm install image-operator image-operator/image-operator -n image-operator --create-namespace

稍等片刻后查看 image-operator 的状态:

1
2
3
$ kubectl get pods -n image-operator
NAME READY STATUS RESTARTS AGE
image-operator-b8c8d5ddb-zkv6l 1/1 Running 0 2m

到这里 image-operator 就安装完成了。

重写镜像地址

image-operator 使用 Rule CRD 来配置重写规则,下面来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: image.lin2ur.cn/v1
kind: Rule
metadata:
name: dockerhub
spec:
rewrite: # <- 重写规则,可以配置多个
- registry: docker.io # <- 需要重写的镜像仓库
replacement: docker.mymirror.com # <- 重写目标镜像仓库
# 使用正则表达式重写,等效于上面的配置
- regex: ^docker\.io/(.*)$
replacement: docker.mymirror.com/$1

将上面的配置保存为 rule.yaml 并应用,然后使用 Dockerhub 仓库的镜像创建一个应用:

1
2
3
4
5
6
7
8
$ kubectl apply -f rule.yaml
rule/dockerhub created

$ kubectl run image-operator-test --image=nginx # <- 使用 Dockerhub 仓库的镜像
pod/image-operator-test created

$ kubectl get pod image-operator-test '-o=jsonpath={.spec.containers[0].image}'
docker.mymirror.com/library/nginx:latest # <- 镜像地址已被重写

此外 image-operator 还支持对特定的 Pod 进行重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: image.lin2ur.cn/v1
kind: Rule
metadata:
name: dockerhub
spec:
namespaceSelector: # <- 选择需要重写的 Namespace
matchExpressions:
- key: kubernetes.io/metadata.name # <- 排除 kube-system 命名空间下的 Pods
operator: NotIn
values:
- kube-system
podSelector: # <- 选择需要重写的 Pod
matchLabels: # <- 仅对包含 rewrite=true 标签的 Pod 进行重写
rewrite: 'true'

同步镜像

如果只是用到少量 Dockerhub 镜像并且不想大费周章搭建镜像仓库,可以使用 Mirror CRD 将 Dockerhub 上的镜像同步到自己的镜像仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: image.lin2ur.cn/v1
kind: Mirror
metadata:
generateName: nginx-
spec:
images:
- source: nginx # <- 源镜像
target: myregistry.com/nginx # <- 目标镜像
tags: # <- 镜像标签
- '1.27'
platforms: # <- (可选) 镜像架构,默认为全部架构
- linux/amd64
dockerConfig: # <- (可选) Docker 配置,用于私有仓库的拉取和推送
secretName: '' # <- 必须是 `kubernetes.io/dockerconfigjson` 类型的 Secret
httpProxy: '' # <- (可选) 拉取镜像时使用 HTTP 代理
parallelism: 1 # <- (可选) 并发数,默认为 1
sizeLimit: 1Gi # <- (可选) 任务 Pod 的临时存储大小
activeDeadlineSeconds: 300 # <- (可选) 任务 Pod 的超时时间

将上面的配置保存为 mirror.yaml 并应用:

1
2
$ kubectl create -f mirror.yaml 
mirror.image.lin2ur.cn/nginx-pq9dg created

image-operator 会创建一个 Job 来同步镜像,稍等片刻后查看任务状态:

1
2
3
$ kubectl get mirror nginx-pq9dg
NAME IMAGES RUNNING FAILED SUCCEEDED
nginx-pq9dg 0 0 2

同步完成后就可以使用 myregistry.com/nginx:1.27 这个镜像了。