﻿# 當 Kubernetes 或 MicroK8s 遇到 Docker Hub 下載限制時的解決之道

我這幾個星期在一間企業進行為期五天的 Kubernetes 教育訓練，我們的 Lab 環境都是用輕量級的 [MicroK8s](https://microk8s.io/) 為主，但是課程一開始就不太順利，因為上課人數有 20 位左右，他們都使用公司的網路，大家都共用一個 IP 連外，以致於碰上討人厭的 [Rate limit on Docker Hub](https://docs.docker.com/docker-hub/download-rate-limit/) 問題，所以有些人無法成功架設起叢集，原因就是 image 下載不到造成的。這篇文章我就來說明在 MicroK8s 如何正確的解決 Docker Hub 無法下載 image 的問題。

### 問題檢測

當問題發生時，你用 `kubectl get po` 應該會看到 `ErrImagePull` 或 `ImagePullBackOff` 字樣，這代表 Image 在拉取的時候發生異常。

此時你可以用 `kubectl describe po/$PODNAME` 查看 `Events` 事件資料，藉此判斷問題發生的原因，錯誤訊息如下：

> Warning Failed 19m (x2 over 19m) kubelet Failed to pull image "docker.io/calico/cni:v3.19.1": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/calico/cni:v3.19.1": failed to copy: httpReadSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/calico/cni/manifests/sha256:f301171be0add870152483fcce71b28cafb8e910f61ff003032e9b1053b062c4: 429 Too Many Requests - Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit

> 這個 `kubectl describe` 是我們在 Kubernetes 做 Troubleshooing 的時候一定會用的命令！

### 解決方案

其實解決方案有非常多種，你可以從 MicroK8s 官網的 [DockerHub download rate limits](https://microk8s.io/docs/dockerhub-limits) 文件看到各種解決方案。

1.  直接設定登入 DockerHub 通過 pull image 的驗證
2.  使用替代的 Registry 來源，透過編輯現有 Deployment 的內容來修正問題
3.  使用自行架設的 Private image registry 並用代理伺服器的方式快取 DockerHub 的 image 內容

本篇文章我主要分享登入 DockerHub 的兩種標準作法。

### 解決方案一：調整 containerd 的 Docker Hub Registry 設定

由於 MicroK8s 主要採用 [containerd](https://containerd.io/) 做為容器引擎 (Container Runtime)，所以最一勞永逸的方式，就是直接調整「每個節點」的 `containerd` 設定，讓任何要從 Docker Hub 下載的 image 都可以輸入 Docker Hub 的認證資訊(帳號密碼)，以解除 Docker Hub 的 [Rate limit](https://docs.docker.com/docker-hub/download-rate-limit/) 限制。

以下是設定步驟：

1.  先取得 [Docker Hub](https://hub.docker.com/) 的帳號與密碼
    
    設定時建議不要直接使用你在 Docker Hub 的密碼，而是到 Docker Hub 的 [Security](https://docs.docker.com/security/access-tokens/personal-access-tokens/) 設定頁面，建立一個 **Access Token** 來當作密碼，建立時可以在 Access permissions 選擇 `Read-only` 即可，確保你的帳戶安全！
    
    ![Docker Hub > Settings > Security](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/2387f45fbeff447560cf-184577339-1adf074c-4f27-494e-9b69-0135a20d95c1.png)
    
2.  編輯叢集節點中的 `/var/snap/microk8s/current/args/containerd-template.toml` 檔案
    
    ```bash
    multipass exec 'microk8s-vm' -- vi /var/snap/microk8s/current/args/containerd-template.toml
    ```
    
    剛進入 `vim` 編輯器的時候，由於 Vim 預設 theme 的設定會讓你看不太清楚部分文字，建議可以輸入以下命令讓文字可以顯示的更清晰：
    
    ```
    :set background=dark
    ```
    
    接著在該檔案的最下方，新增以下內容： (排版沒有很重要)
    
    ```ini
    [plugins."io.containerd.grpc.v1.cri".registry.configs."registry-1.docker.io".auth]
      username = "DOCKERHUB_Username"
      password = "DOCKERHUB_AccessToken"
    ```
    
    > 請務必在每個節點都做一樣的設定，但叢集在升級後是否會保留設定，我還沒測試過，我會等下次升級的時候測試看看！
    
    以下是另一種不用開 `vim` 就可以將內容加入 `/var/snap/microk8s/current/args/containerd-template.toml` 的技巧：
    
    先進入 VM 的 Shell 環境：
    
    ```bash
    multipass shell microk8s-vm
    ```
    
    再將內容一次加入：
    
    ```bash
    cat <<EOF >> /var/snap/microk8s/current/args/containerd-template.toml
      [plugins."io.containerd.grpc.v1.cri".registry.configs."registry-1.docker.io".auth]
        username = "DOCKERHUB_Username"
        password = "DOCKERHUB_AccessToken"
    EOF
    ```
    
3.  重新啟動 MicroK8s 即可
    
    ```bash
    microk8s stop
    microk8s start
    ```
    
4.  測試部署應用程式
    
    ```bash
    kubectl create deployment microbot --image=dontrebootme/microbot:v1
    kubectl expose deployment microbot --type=NodePort --port=80 --name=microbot-service
    kubectl describe pod
    ```
    

### 解決方案二：使用自訂的 Private Registry 當成 Image 來源

我們自己與我們的客戶很多都會自行架設 Private Registry 當成 Image 來源，像是 ACR ([Azure Container Registry](https://azure.microsoft.com/zh-tw/services/container-registry/?WT.mc_id=DT-MVP-4015686)) 或 [Harbor](https://goharbor.io/) 等等。當你要部署自行製作並上傳到 Private Registry 的 Image 時，就一定需要通過身份驗證，否則你將無法下載任何 Image 回來。

以下是設定步驟：

1.  先取得 Private Registry 的帳號與密碼
    
    這裡我一樣以 [Docker Hub](https://hub.docker.com/) 為例，設定時建議不要直接使用你在 Docker Hub 的密碼，而是到 Docker Hub 的 [Security](https://docs.docker.com/security/access-tokens/personal-access-tokens/) 設定頁面，建立一個 **Access Token** 來當作密碼，建立時可以在 Access permissions 選擇 `Read-only` 即可，確保你的帳戶安全！
    
2.  建立一個名為 `regcred` 的 [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) 物件
    
    Bash
    
    ```bash
    kubectl create secret docker-registry regcred \
      --docker-server="https://index.docker.io/v1/" \
      --docker-username="<Username>" \
      --docker-password="<AccessToken>" \
      --docker-email="<Email>"
    ```
    
    PowerShell
    
    ```powershell
    kubectl create secret docker-registry regcred `
      --docker-server="https://index.docker.io/v1/" `
      --docker-username="<Username>" `
      --docker-password="<AccessToken>" `
      --docker-email="<Email>"
    ```
    
    建立完成後，你可以透過以下命令看到實際的設定內容：
    
    ```bash
    kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode | jq -C
    ```
    
    取得當初設定的帳號與密碼
    
    ```bash
    kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d | jq '.auths."https://index.docker.io/v1/".username' -Cr
    kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d | jq '.auths."https://index.docker.io/v1/".password' -Cr
    ```
    
3.  在 Pod Template 之中的 Container Spec 指定 `imagePullSecrets` 欄位為 `regcred` 即可
    
    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: private-reg
    spec:
      containers:
        - name: private-reg-container
          image: <your-private-image>
      imagePullSecrets:
        - name: regcred
    ```
    

設定好之後，你的 Pod 在啟動時，就可以正確的從 Private Registry 下載 Image 了！ 👍

### 相關連結

-   [MicroK8s - DockerHub download rate limits](https://microk8s.io/docs/dockerhub-limits)
-   [DockerHub download rate limits - General Discussions / microk8s - Discuss Kubernetes](https://discuss.kubernetes.io/t/dockerhub-download-rate-limits/18729)
-   [Download rate limit | Docker Documentation](https://docs.docker.com/docker-hub/download-rate-limit/)
-   [Pull an Image from a Private Registry | Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/)
-   [How to assign password with env variables when pulling images? · Discussion #7269 · containerd/containerd](https://github.com/containerd/containerd/discussions/7269)
-   [Registry as a pull through cache | Docker Documentation](https://docs.docker.com/registry/recipes/mirror/)
-   [MicroK8s - How to use the built-in registry](https://microk8s.io/docs/registry-built-in)
-   [Service management | Snapcraft documentation](https://snapcraft.io/docs/service-management)
