V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yangchuansheng33
V2EX  ›  推广

彻底解决 gcr、quay、DockerHub 镜像下载难题

  •  
  •   yangchuansheng33 · 2020-06-30 10:19:36 +08:00 · 5935 次点击
    这是一个创建于 1637 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文链接:https://fuckcloudnative.io/posts/docker-registry-proxy/

    在使用 Docker 和 Kubernetes 时,我们经常需要访问 gcr.ioquay.io 镜像仓库,由于众所周知的原因,这些镜像仓库在中国都无法访问,唯一能访问的是 Docker Hub,但速度也是奇慢无比。gcr.azk8s.cngcr.io 镜像仓库的代理站点,原来可以通过 gcr.azk8s.cn 访问 gcr.io 仓库里的镜像,但是目前 *.azk8s.cn 已经仅限于 Azure 中国的 IP 使用,不再对外提供服务了。国内其他的镜像加速方案大多都是采用定时同步的方式来缓存,不能保证及时更新,ustc 和七牛云等镜像加速器我都试过了,非常不靠谱,很多镜像都没有。

    为了能够顺利访问 gcr.io 等镜像仓库,我们需要在墙外自己搭建一个类似于 gcr.azk8s.cn 的镜像仓库代理站点。直接反代可以保证获取到的镜像是最新最全的,比缓存靠谱多了。

    1. 前提条件


    • 一台能够施展魔法的服务器(你懂得,可以直接访问 gcr.io
    • 一个域名和域名相关的 SSL 证书( docker pull 镜像时需要验证域名证书),一般用 Let's Encrypt 就够了。

    2. 代理方案


    quay.ioDocker Hub 很好代理,可以直接使用 Envoyhost_rewrite_literal 参数(这是新版本的参数,如果你使用旧版本的 Envoy,参数应该是 host_rewrite ),当 Envoy 将请求转发给上游集群时,会直接将头文件中的 host 改为指定的值。比如,如果上游集群是 quay.io ,就将头文件改为 quay.io。我之前写过的 使用 Envoy 反向代理谷歌搜索 用的就是此方案。什么?你是 Envoy 小白?莫慌,我已经为你们准备了一本 Envoy 从入门到放弃 的电子书,快快点击下方的链接学习去吧(记得给我一个 star 哦)~~

    gcr.io 稍微有点难办,因为它在连接的时候需要二次认证,即使你通过反代服务器 pull 镜像,它还是会再次访问 gcr.io 进行验证,然后才可以通过反代服务器 pull 镜像。这就有点尴尬了,我特么要是能访问 gcr.io ,还要什么反代啊。。。话说 Docker 官方不是有一个 registry 镜像吗,可以通过设置参数 remoteurl 将其作为远端仓库的缓存仓库,这样当你通过这个私有仓库的地址拉取镜像时,regiistry 会先将镜像缓存到本地存储,然后再提供给拉取的客户端(有可能这两个步骤是同时的,我也不太清楚)。我们可以先部署一个私有 registry,然后将 remoteurl 设为 https://gcr.io,最后再通过 Envoy 反代,基本上就可以了。

    3. 代理配置


    方案确定了之后,就可以动手配置了。还是使用我之前反复提到的方法:通过文件动态更新配置。如果你不是很清楚我在说什么,请参考 Envoy 基础教程:基于文件系统动态更新配置。这里我就直接贴配置了。

    bootstrap 配置:

    # envoy.yaml
    node:
      id: node0
      cluster: cluster0
    dynamic_resources:
      lds_config:
        path: /etc/envoy/lds.yaml
      cds_config:
        path: /etc/envoy/cds.yaml
    admin:
      access_log_path: "/dev/stdout"
      address:
        socket_address:
          address: "0.0.0.0"
          port_value: 15001
    

    LDS 的配置:

    # lds.yaml
    version_info: "0"
    resources:
    - "@type": type.googleapis.com/envoy.config.listener.v3.Listener
      name: listener_http
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 80
      filter_chains:
      - filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            stat_prefix: ingress_http
            codec_type: AUTO
            access_log:
              name: envoy.access_loggers.file
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                path: /dev/stdout
            route_config:
              name: http_route
              virtual_hosts:
              - name: default
                domains:
                - "*"
                routes:
                - match:
                    prefix: "/"
                  redirect:
                    https_redirect: true
                    port_redirect: 443
                    response_code: "FOUND"
            http_filters:
            - name: envoy.filters.http.router
    - "@type": type.googleapis.com/envoy.config.listener.v3.Listener
      name: listener_https
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 443
      listener_filters:
      - name: "envoy.filters.listener.tls_inspector"
        typed_config: {}
      filter_chains:
      - transport_socket:
          name: envoy.transport_sockets.tls
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
            common_tls_context:
              alpn_protocols: h2,http/1.1
              tls_certificates:
              - certificate_chain:
                  filename: "/root/.acme.sh/xxx.com/fullchain.cer"
                private_key:
                  filename: "/root/.acme.sh/xxx.com/xxx.com.key"
        filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            stat_prefix: ingress_https
            codec_type: AUTO
            use_remote_address: true
            access_log:
              name: envoy.access_loggers.file
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                path: /dev/stdout
            route_config:
              name: https_route
              response_headers_to_add:
              - header:
                  key: Strict-Transport-Security
                  value: "max-age=15552000; includeSubdomains; preload"
              virtual_hosts:
              - name: gcr
                domains:
                - gcr.xxx.com
                routes:
                - match:
                    prefix: "/"
                  route:
                    cluster: gcr
                    timeout: 600s
              - name: quay
                domains:
                - quay.xxx.com
                routes:
                - match:
                    prefix: "/"
                  route:
                    cluster: quay
                    host_rewrite_literal: quay.io
              - name: docker
                domains:
                - docker.xxx.com
                routes:
                - match:
                    prefix: "/"
                  route:
                    cluster: dockerhub
                    host_rewrite_literal: registry-1.docker.io
            http_filters:
            - name: envoy.filters.http.router
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
    

    友情提醒:我这里使用的是 v3 版本的 API,v2 版本的 API 即将被废弃,请奔走相告。

    CDS 的配置:

    # cds.yaml
    version_info: "0"
    resources:
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: gcr
      connect_timeout: 1s
      type: strict_dns
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: gcr
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: gcr.default
                  port_value: 5000
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: dockerhub
      connect_timeout: 15s
      type: logical_dns
      dns_lookup_family: V4_ONLY
      dns_resolvers:
        socket_address:
          address: 8.8.8.8
          port_value: 53
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: dockerhub
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: registry-1.docker.io
                  port_value: 443
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          sni: registry-1.docker.io
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: quay
      connect_timeout: 15s
      type: logical_dns
      dns_lookup_family: V4_ONLY
      dns_resolvers:
        socket_address:
          address: 8.8.8.8
          port_value: 53
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: quay
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: quay.io
                  port_value: 443
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          sni: quay.io
    

    各个字段的含义我实在是懒得解释,可以直接去看上面提到的电子书。

    配置好了 Envoy 之后,就可以通过代理服务器拉取 Docker Hubquay.io 的镜像了。最后来解决 gcr.io 镜像的难题。

    4. registry 配置


    首先需要部署一个私有的 registry,如果你只有一台服务器(我想大多数人应该只会买一台吧),可以使用 docker-compose,我这里是使用 Kubernetes 部署的,首先需要准备一个部署清单:

    # registry-proxy.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gcr
      labels:
        app: gcr
    spec:
      replicas: 1 
      selector:
        matchLabels:
          app: gcr
      template:
        metadata:
          labels:
            app: gcr
        spec:
          nodeSelector:
            kubernetes.io/hostname: blog-k3s03
          tolerations:
          - key: node-role.kubernetes.io/ingress
            operator: Exists
            effect: NoSchedule
          hostNetwork: false 
          containers:
          - name: gcr
            image: findsec/registry-proxy:latest 
            env:
            - name: PROXY_REMOTE_URL
              value: https://gcr.io
            ports:
            - containerPort: 5000
              hostPort: 5000
              protocol: TCP
            volumeMounts:
            - mountPath: /etc/localtime
              name: localtime
            - mountPath: /var/lib/registry
              name: registry
          volumes:
          - name: localtime
            hostPath:
              path: /etc/localtime
          - name: registry
            hostPath:
              path: /var/lib/registry
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: gcr
      labels:
        app: gcr
    spec:
      sessionAffinity: ClientIP
      selector:
        app: gcr
      ports:
        - protocol: TCP
          name: http
          port: 5000 
          targetPort: 5000 
    

    然后将其部署到 Kubernetes 集群中:

    $ kubectl apply -f registry-proxy.yaml
    

    现在再回过头来看看 Envoy 的配置中关于 gcr 的部分,先来看看 LDS

    virtual_hosts:
    - name: gcr
      domains:
      - gcr.xxx.com
      routes:
      - match:
          prefix: "/"
        route:
          cluster: gcr
          timeout: 600s
    

    很简单,不需要解释,再来看看 CDS:

    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: gcr
      connect_timeout: 1s
      type: strict_dns
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: gcr
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: gcr.default
                  port_value: 5000
    

    这里的 address 使用的是 Kubernetes 集群内部域名,其他部署方式请自己斟酌。

    最后,给服务器换个新内核,开启 BBR 加速不过分吧?不然你的镜像拉取仍然是龟速。

    5. Docker 配置


    现在你就可以通过代理服务器来拉取公共镜像了。

    对于 Docker Hub 来说,只需要将 docker.io 换成 docker.xxx.com 就行了,比如你想拉取 nginx:alpine 镜像,可以使用下面的命令:

    🐳 → docker pull docker.xxx.com/library/nginx:alpine
    

    对于 quay.io 来说,只需要将 quay.io 换成 quay.xxx.com 就行了,比如你想拉取 quay.io/coreos/kube-state-metrics:v1.5.0 镜像,可以使用下面的命令:

    🐳 → docker pull quay.xxx.com/coreos/kube-state-metrics:v1.5.0
    

    对于 gcr.io 来说,只需要将 gcr.io 换成 gcr.xxx.com 就行了,比如你想拉取 gcr.io/google-containers/etcd:3.2.24 镜像,可以使用下面的命令:

    🐳 → docker pull gcr.xxx.com/google-containers/etcd:3.2.24
    

    当然,Docker 是可以设置 registry mirror 的,但只支持 Docker Hub。可以修改配置文件 /etc/docker/daemon.json,添加下面的内容:

    {
        "registry-mirrors": [
            "https://docker.xxx.com"
        ]
    }
    

    然后重启 Docker 服务,就可以直接拉取 Docker Hub 的镜像了,不需要显示指定代理服务器的地址,Docker 服务本身会自动通过代理服务器去拉取镜像。比如:

    🐳 → docker pull nginx:alpine
    🐳 → docker pull docker.io/library/nginx:alpine
    

    Containerd 就比较简单了,它支持任意 registry 的 mirror,只需要修改配置文件 /etc/containerd/config.toml,添加如下的配置:

    [plugins.cri.registry.mirrors]
      [plugins.cri.registry.mirrors."docker.io"]
        endpoint = ["https://docker.xxx.com"]
      [plugins.cri.registry.mirrors."quay.io"]
        endpoint = ["https://quay.xxx.com"]
      [plugins.cri.registry.mirrors."gcr.io"]
        endpoint = ["http://gcr.xxx.com"]
    

    重启 Containerd 服务后,就可以直接拉取 Docker Hubgcr.ioquay.io 的镜像了,不需要修改任何前缀,Containerd 会根据配置自动选择相应的代理 URL 拉取镜像。

    6. 费用评估


    好了,现在我们来评估一下这一切的费用。首先你得有一个会魔法的服务器,国内的肯定不用考虑了,必须选择国外的,而且到国内的速度还过得去的,最低最低不会低于 30 人民币 /月 吧。除此之外,你还得拥有一个个人域名,这个价格不好说,总而言之,加起来肯定不会低于 30 吧,多数人肯定是下不去这个手的。没关系,我有一个更便宜的方案,我已经部署好了一切,你可以直接用我的服务,当然我也是自己买的服务器,每个月也是要花钱的,如果你真的想用,只需要每月支付 3 元,以此来保障我每个月的服务器费用。当然肯定不止你一个人,我可能还会赚点钱,但最多不允许超过 40 人,如果人数特别多,我可能会考虑加服务器。这个需要你自己考虑清楚,有意者扫描下方的二维码加入交流群:

    gcr 镜像加速服务

    如果二维码已过期,可以加我微信拉你进群。

    33 条回复    2020-06-30 23:52:26 +08:00
    guyeu
        1
    guyeu  
       2020-06-30 10:50:00 +08:00   ❤️ 3
    @Livid 这是广告吧
    marquina
        2
    marquina  
       2020-06-30 10:52:25 +08:00 via Android   ❤️ 1
    彻底解决(指透明代理
    privil
        3
    privil  
       2020-06-30 11:00:14 +08:00
    搞那么复杂……我只需搞一个稳定的 http 代理 https://lakelight.net/2019/03/27/k8s-cache.html
    yangchuansheng33
        4
    yangchuansheng33  
    OP
       2020-06-30 11:36:08 +08:00
    @privil 你和我的方法是一个意思,只不过我把配置文件都贴出来了,看起来比较多而已
    defunct9
        5
    defunct9  
       2020-06-30 11:36:55 +08:00
    搞得这么复杂,为了炫技而炫技,纯属无聊
    yangchuansheng33
        6
    yangchuansheng33  
    OP
       2020-06-30 11:39:16 +08:00
    @marquina 你要知道,绝大多数人是不懂透明代理的玩法的,我可以直接喂到他们嘴里
    pmispig
        7
    pmispig  
       2020-06-30 11:39:34 +08:00
    纯粹是为了赚钱吧,最方便的就是开个临时实例推到 dockerhub
    isnullstring
        8
    isnullstring  
       2020-06-30 11:39:52 +08:00
    还行,适合懒得动手
    yangchuansheng33
        9
    yangchuansheng33  
    OP
       2020-06-30 11:40:43 +08:00
    @defunct9 复杂要看对谁而言,对用户来说一点也不复杂,直接用就好了
    yangchuansheng33
        10
    yangchuansheng33  
    OP
       2020-06-30 11:41:08 +08:00
    @pmispig 你自己算一下我能不能赚钱
    yangchuansheng33
        11
    yangchuansheng33  
    OP
       2020-06-30 11:42:07 +08:00 via Android
    @pmispig 关键是 dockerhub 也慢
    yov123456
        12
    yov123456  
       2020-06-30 12:27:05 +08:00 via iPhone
    surge 开个增强模式(
    whileFalse
        13
    whileFalse  
       2020-06-30 13:25:00 +08:00
    @yov123456 服务器怎么办,在云上装个黑苹果?🐶
    Rwing
        14
    Rwing  
       2020-06-30 13:28:30 +08:00
    挺好的 支持一下
    herozzm
        15
    herozzm  
       2020-06-30 13:29:53 +08:00 via Android
    路由器全局爬梯子
    listenerri
        16
    listenerri  
       2020-06-30 14:38:38 +08:00   ❤️ 1
    感觉还行吧,不能算是广告,楼主解决了一个普遍痛点,但由于为此要付出一些代价,所以找有相同痛点的人一起分担一下,可以理解;但是我觉得有点标题党,我进来时想要发现点奇技淫巧的,结果你跟我说让我用你的代理吧。
    shenlanAZ
        17
    shenlanAZ  
       2020-06-30 14:46:49 +08:00
    为啥不能再网线前面插个透明代理 这样你连 google 的时候都不用再进行配置了。

    而且 不需要域名 不需要这么多复杂的步骤。。。。。

    解决问题从简单的方式解决不好么。。。
    somefree
        18
    somefree  
       2020-06-30 14:52:32 +08:00   ❤️ 1
    看了看回复, 好像没人提镜像加速站的, 补充一个

    ```
    # 第一行 原始的镜像坐标
    # 第二行 加速后的镜像坐标

    docker pull xxx:yyy
    docker pull dockerhub.azk8s.cn/library/xxx:yyy

    docker pull xxx/yyy:zz
    docker pull dockerhub.azk8s.cn/xxx/yyy:zz

    docker pull gcr.io/xxx/yyy:zzz
    docker pull gcr.azk8s.cn/xxx/yyy:zzz

    docker pull k8s.gcr.io/xxx:yyy
    docker pull gcr.azk8s.cn/google-containers/xxx:yyy

    docker pull quay.io/xxx/yyy:zzz
    docker pull quay.azk8s.cn/xxx/yyy:zzz
    ```
    millson
        19
    millson  
       2020-06-30 14:55:54 +08:00
    @somefree azure 的已经不提供外部访问
    somefree
        20
    somefree  
       2020-06-30 15:07:00 +08:00
    @millson 果然欸, 好久没碰过了, 但是也还有别的加速站点呀, 查了查, 网易加速:hub-mirror.c.163.com, DaoCloud 道客云:daocloud.io, 不过我还没试过这两个
    dullwit
        21
    dullwit  
       2020-06-30 15:10:14 +08:00
    docker 跑个 clash,写个 iptables 规则,局域网和 docker 的流量都进 tun
    mcone
        22
    mcone  
       2020-06-30 16:38:23 +08:00   ❤️ 1
    @Livid 疑似推广
    至少不适合这个节点
    yangchuansheng33
        23
    yangchuansheng33  
    OP
       2020-06-30 16:53:28 +08:00 via Android
    @listenerri 并没有啊,我告诉你方案了,我只是说如果你舍不得花那么多钱,才用我的啊
    yangchuansheng33
        24
    yangchuansheng33  
    OP
       2020-06-30 16:55:40 +08:00 via Android
    @shenlanAZ 你思考的方向有问题,对于我来说这样是很简单,对于大众来说,很少人会这个,我的思考方向是大众,顺便一个月可能还能赚回其他 VPS 的钱(赚多少钱不可能,如果人多撑死了几十块),多好
    Illusionary
        25
    Illusionary  
       2020-06-30 16:57:37 +08:00
    图样,有境外服务器谁不会弄? 直接在路由器配置境外 ip 走代理就完事了,搞这些花里胡哨
    yangchuansheng33
        26
    yangchuansheng33  
    OP
       2020-06-30 17:01:53 +08:00 via Android
    @Illusionary 看我上面的回复,这些方案只适合我个人用,不适合我推广给大众
    chenxinwri9212
        27
    chenxinwri9212  
       2020-06-30 17:02:04 +08:00
    @Illusionary (づ。◕ᴗᴗ◕。)づ
    yangchuansheng33
        28
    yangchuansheng33  
    OP
       2020-06-30 17:03:13 +08:00 via Android
    @Illusionary 你说的这些你觉得我不知道吗?可以看我网站置顶的博客
    yangchuansheng33
        29
    yangchuansheng33  
    OP
       2020-06-30 17:08:43 +08:00 via Android
    @somefree 这些 dockerhub 的镜像都不全,且不支持 gcr 和 quay
    cs419
        30
    cs419  
       2020-06-30 19:24:55 +08:00
    通常搭 k8s 的时候需要访问 gcr 仓库
    这 envoy 直接部署到 k8s 上做反代??
    所以说:是先有鸡,还是先有蛋

    大部分人都用的 nginx
    这里还得学 envoy
    人生苦短,envoy 是拥有远超 nginx 的性能?
    yangchuansheng33
        31
    yangchuansheng33  
    OP
       2020-06-30 20:05:14 +08:00 via Android
    @cs419 仔细看完了再问问题,我没有怂恿大家部署到 k8s 上,毕竟大多数人没有那么多公有云资源,我只是描述了我自己是这么干的。

    关于 envoy,见仁见智,你用习惯了 Nginx 可以继续用,我用不习惯就用 envoy,这个不重要,我只是提供一个思路
    d5
        32
    d5  
       2020-06-30 20:12:43 +08:00
    @somefree “从小”就用 daocloud,dao xxx 大法好,哈哈哈哈
    icecrack
        33
    icecrack  
       2020-06-30 23:52:26 +08:00
    楼主教程不错,如果自己不想搭,还有现成服务。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1057 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 19:21 · PVG 03:21 · LAX 11:21 · JFK 14:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.