banner
Lainbo

Lainbo's Blog

If you’re nothing without the suit, then you shouldn't have it.
github

自分のDocker Hubイメージの作成方法

2023 年 5 月中旬以来、神秘な力によって Docker コンテナプラットフォーム https://hub.docker.com にアクセスできなくなりました。

1 年以上が経過し、現在(2024 年 6 月 9 日)南京大学、中科大、上海交通大学は docker イメージキャッシュサービスを明確に停止しています。网易は以前に終了し、腾讯と微软は内網で利用可能とのこと、阿里はログイン後にサブドメインを取得でき、百度もどうやら終了したようで、dockerproxy はブロックされています。

それでは、自分で構築してみましょう!(微笑)

この記事で紹介する 2 つの方法は完全にゼロのハードルではありません。方法 1 は自分のドメインが必要で、方法 2 は自分の海外サーバーが必要です。

方法 1:Cloudflare Worker を使用する#

Cloudflareの登録方法は繰り返し説明しませんが、メールアドレスがあれば登録できます。この方法では、自分のドメインが必要です。

  1. メニューバーの「Worker と Pages」をクリックし、「Worker を作成」をクリックします。
    image

  2. Worker に名前を付けます(例:docker-proxy)、そして「保存」をクリックし、その後「完了」をクリックします。

    この時、"おめでとうございます!あなたの Worker は以下の地域にデプロイされました:全世界" と表示されるはずです。

  3. 右側の「コードを編集」をクリックし、左側の既存のコードを削除して、以下のコードを貼り付けます。最上部のworkers_urlをデプロイするドメインに変更する必要があります。

    • コード内容
      'use strict'
      
      const hub_host = 'registry-1.docker.io'
      const auth_url = 'https://auth.docker.io'
      const workers_url = 'https://あなたのドメイン'
      //const home_page_url = 'リモートhtmlリンク'
      
      /** @type {RequestInit} */
      const PREFLIGHT_INIT = {
          status: 204,
          headers: new Headers({
              'access-control-allow-origin': '*',
              'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
              'access-control-max-age': '1728000',
          }),
      }
      
      /**
       * @param {any} body
       * @param {number} status
       * @param {Object<string, string>} headers
       */
      function makeRes(body, status = 200, headers = {}) {
          headers['access-control-allow-origin'] = '*'
          return new Response(body, {status, headers})
      }
      
      /**
       * @param {string} urlStr
       */
      function newUrl(urlStr) {
          try {
              return new URL(urlStr)
          } catch (err) {
              return null
          }
      }
      
      addEventListener('fetch', e => {
          const ret = fetchHandler(e)
              .catch(err => makeRes('cfworker error:\n' + err.stack, 502))
          e.respondWith(ret)
      })
      
      /**
       * @param {FetchEvent} e
       */
      async function fetchHandler(e) {
          const getReqHeader = (key) => e.request.headers.get(key);
      
          let url = new URL(e.request.url);
      
          if (url.pathname === '/') {
              // ホームページのHTMLコンテンツを取得して返す
              //return fetch(home_page_url);
              return new Response(indexHtml, {
                  headers: {
                    'Content-Type': 'text/html'
                  }
                });
          }
      
          if (url.pathname === '/token') {
              let token_parameter = {
                  headers: {
                      'Host': 'auth.docker.io',
                      'User-Agent': getReqHeader("User-Agent"),
                      'Accept': getReqHeader("Accept"),
                      'Accept-Language': getReqHeader("Accept-Language"),
                      'Accept-Encoding': getReqHeader("Accept-Encoding"),
                      'Connection': 'keep-alive',
                      'Cache-Control': 'max-age=0'
                  }
              };
              let token_url = auth_url + url.pathname + url.search
              return fetch(new Request(token_url, e.request), token_parameter)
          }
      
          url.hostname = hub_host;
      
          let parameter = {
              headers: {
                  'Host': hub_host,
                  'User-Agent': getReqHeader("User-Agent"),
                  'Accept': getReqHeader("Accept"),
                  'Accept-Language': getReqHeader("Accept-Language"),
                  'Accept-Encoding': getReqHeader("Accept-Encoding"),
                  'Connection': 'keep-alive',
                  'Cache-Control': 'max-age=0'
              },
              cacheTtl: 3600
          };
      
          if (e.request.headers.has("Authorization")) {
              parameter.headers.Authorization = getReqHeader("Authorization");
          }
      
          let original_response = await fetch(new Request(url, e.request), parameter)
          let original_response_clone = original_response.clone();
          let original_text = original_response_clone.body;
          let response_headers = original_response.headers;
          let new_response_headers = new Headers(response_headers);
          let status = original_response.status;
      
          if (new_response_headers.get("Www-Authenticate")) {
              let auth = new_response_headers.get("Www-Authenticate");
              let re = new RegExp(auth_url, 'g');
              new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
          }
      
          if (new_response_headers.get("Location")) {
              return httpHandler(e.request, new_response_headers.get("Location"))
          }
      
          let response = new Response(original_text, {
              status,
              headers: new_response_headers
          })
          return response;
      
      }
      
      /**
       * @param {Request} req
       * @param {string} pathname
       */
      function httpHandler(req, pathname) {
          const reqHdrRaw = req.headers
      
          // プリフライト
          if (req.method === 'OPTIONS' &&
              reqHdrRaw.has('access-control-request-headers')
          ) {
              return new Response(null, PREFLIGHT_INIT)
          }
      
          let rawLen = ''
      
          const reqHdrNew = new Headers(reqHdrRaw)
      
          const refer = reqHdrNew.get('referer')
      
          let urlStr = pathname
      
          const urlObj = newUrl(urlStr)
      
          /** @type {RequestInit} */
          const reqInit = {
              method: req.method,
              headers: reqHdrNew,
              redirect: 'follow',
              body: req.body
          }
          return proxy(urlObj, reqInit, rawLen, 0)
      }
      
      /**
       *
       * @param {URL} urlObj
       * @param {RequestInit} reqInit
       */
      async function proxy(urlObj, reqInit, rawLen) {
          const res = await fetch(urlObj.href, reqInit)
          const resHdrOld = res.headers
          const resHdrNew = new Headers(resHdrOld)
      
          // 検証
          if (rawLen) {
              const newLen = resHdrOld.get('content-length') || ''
              const badLen = (rawLen !== newLen)
      
              if (badLen) {
                  return makeRes(res.body, 400, {
                      '--error': `bad len: ${newLen}, except: ${rawLen}`,
                      'access-control-expose-headers': '--error',
                  })
              }
          }
          const status = res.status
          resHdrNew.set('access-control-expose-headers', '*')
          resHdrNew.set('access-control-allow-origin', '*')
          resHdrNew.set('Cache-Control', 'max-age=1500')
      
          resHdrNew.delete('content-security-policy')
          resHdrNew.delete('content-security-policy-report-only')
          resHdrNew.delete('clear-site-data')
      
          return new Response(res.body, {
              status,
              headers: resHdrNew
          })
      }
      
      const indexHtml = `
      <!DOCTYPE html>
      <html lang="zh-CN">
      
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Linux上でDocker Hub Registry Mirrorを設定する</title>
        <style>
          body {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
            color: #333;
            background-color: #f7f7f7;
          }
      
          h1 {
            color: #0066cc;
            margin-bottom: 30px;
          }
      
          h2 {
            color: #0066cc;
            margin-top: 40px;
          }
      
          pre {
            background-color: #fff;
            padding: 15px;
            padding-top: 48px;
            overflow-x: auto;
            border-radius: 8px;
            box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
          }
      
          ol {
            margin-top: 20px;
            padding-left: 20px;
          }
      
          li {
            margin-bottom: 10px;
          }
      
          code {
            font-family: Consolas, monospace;
            background-color: #fff;
            padding: 2px 4px;
            border-radius: 3px;
          }
      
          .container {
            background-color: #fff;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
          }
      
          .copy-btn {
            position: absolute;
            top: 10px;
            right: 10px;
            padding: 4px 10px;
            background-color: #0066cc;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.3s;
          }
      
          .copy-btn:hover {
            background-color: #0052a3af;
          }
      
          .code-wrapper {
            position: relative;
          }
        </style>
      </head>
      
      <body>
        <div class="container">
          <h1>Linux上でDocker Hub Registry Mirrorを設定する</h1>
          <p>Dockerイメージのダウンロード速度を向上させるために、Docker Hubのレジストリミラーを設定できます。</p>
      
          <h2>設定手順</h2>
          <ol>
            <li><code>/etc/docker/daemon.json</code>ファイルを作成または編集し、以下の内容を追加します:</li>
          </ol>
          <div class="code-wrapper">
            <pre>
      echo '{"registry-mirrors": ["${workers_url}"]}' | sudo tee /etc/docker/daemon.json > /dev/null</pre>
            <button class="copy-btn" onclick="copyCode(this)">コピー</button>
          </div>
          <ol start="2">
            <li>Dockerサービスを再起動します:</li>
          </ol>
          <div class="code-wrapper">
            <pre>sudo systemctl restart docker</pre>
            <button class="copy-btn" onclick="copyCode(this)">コピー</button>
          </div>
      
          <p>設定が完了すると、Dockerは設定したレジストリミラーからイメージを取得し、イメージのダウンロードプロセスを加速します。</p>
        </div>
      
        <script>
          function copyCode(btn) {
            const pre = btn.previousElementSibling;
            const code = pre.innerText;
            navigator.clipboard.writeText(code).then(function () {
              btn.innerText = 'コピー済み';
              setTimeout(function () {
                btn.innerText = 'コピー';
              }, 2000);
            }, function () {
              alert('コピーに失敗しました。手動でコピーしてください。');
            });
          }
        </script>
      </body>
      
      </html>
      `
      
  4. 右上の「デプロイ」をクリックします。

  5. Worker が提供するドメインは DNS 汚染されているため、Worker に戻り、図のようにクリックして、コードに記入したドメインを入力して変更を提出します。Cloudflare にドメインがホスティングされている場合は、約 2 分待つだけで有効になります。他のサービスプロバイダーにホスティングされている場合は、自分で CNAME を設定する必要があります。
    image

  6. この時、サーバーで以下のコマンドを入力すれば有効になります。

    echo '{"registry-mirrors": ["https://あなたのドメイン"]}' | sudo tee /etc/docker/daemon.json > /dev/null
    
    sudo systemctl restart docker
    

方法 2:海外サーバーを使用して自分で構築する#

手抜きスクリプト#

bash <(curl -sL https://raw.githubusercontent.com/lainbo/gists-hub/master/src/Linux/sh/deploy_registry.sh)

または、自分で一歩ずつ進めたい場合#

  1. docker-compose.yml ファイルを作成し、以下の内容を追加します。

    #version: '3' #最新のdockerバージョンではこのフィールドは必要ありません
    services:
      registry:
        image: registry:2
        ports:
          - "17951:5000"
        environment:
          REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io  # 上流ソース
          REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: inmemory # メモリキャッシュ
        volumes:
          - ./data:/var/lib/registry
    
  2. 実行します、docker-compose up -d


手抜きスクリプトでも、自分で docker-compose ファイルを作成しても、どちらも docker を利用して 17951 ポートでこの自作サービスを立ち上げます。次に行うことは

  1. 上記の 17951 ポートをリバースプロキシし、ドメインを設定し、https 証明書を追加し、DNS 解析にエントリを追加します。

  2. この時、国内サーバーで以下のコマンドを入力すれば有効になります。

    echo '{"registry-mirrors": ["https://あなたのリバースプロキシのドメイン"]}' | sudo tee /etc/docker/daemon.json > /dev/null
    
    sudo systemctl restart docker
    

方法 3:hueckerを使用する#

これは作者が構築した既存のミラーです。

{
  "registry-mirrors": ["https://huecker.io"]
}

他にも試す価値のある海外ミラーがあります。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。