<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>skybirds</title>
  
  
  <link href="https://skybirds.sbs/atom.xml" rel="self"/>
  
  <link href="https://skybirds.sbs/"/>
  <updated>2023-07-09T14:41:01.057Z</updated>
  <id>https://skybirds.sbs/</id>
  
  <author>
    <name>Sky Bird</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Traefik 的基础使用</title>
    <link href="https://skybirds.sbs/2023/traefik/"/>
    <id>https://skybirds.sbs/2023/traefik/</id>
    <published>2023-07-08T16:00:00.000Z</published>
    <updated>2023-07-09T14:41:01.057Z</updated>
    
    <content type="html"><![CDATA[<p>前阵子迁移服务器，想着 Docker 容器挺多的，就试着把反代从 Nginx 换成了 Traefik。虽然主要只用上了比较基础的部分，但感觉挺不错的。Traefik 的一大特点就是对于一堆 Docker 容器，容器配置在自己 label 那儿（简单点一两个 label 就够）。容器启动 Traefik 就能自动发现并应用配置，如果配置过自动的证书申请证书也一起申了，不用启动了服务再单独找个地方改配置、申证书</p><p>不过如果没接触过反代也许从 Nginx 之类入手更好，资料更多且不少文档给出的反代例子都是 Nginx 的，或者希望方便点的话可以看看 Nginx Proxy Manager &#x2F; Caddy 那些。Traefik 可能更难理解一点，适合常用 Docker 的用户</p><p>以及…</p><ul><li>本文纯乱折腾，可能有错误（如果您发现错误请联系我）</li><li>主要涉及（个人）比较常用的部分，毕竟不熟的同样翻文档</li><li>因为 Traefik 文档只有英文，为了避免混淆，本文中关键概念尽量用与文档相同的英文</li></ul><h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><blockquote><p> <a href="https://doc.traefik.io/traefik/routing/overview/">https://doc.traefik.io/traefik/routing/overview/</a></p></blockquote><p>Traefik 的配置分为两类，一种静态一种动态。静态部分主要是一些启动后就固定的部分，比如 Traefik 自己的配置、Entrypoints 和一些证书相关的配置；动态部分则主要和每个服务有关，例如路由规则、用哪个端口之类。静态可以是文件或者命令行参数，动态常用的可能是 Docker 的 label 或者文件（Traefik 可以观察文件变化）</p><p>实际使用静态参数既可以当参数写进 Docker 的启动命令也可以单独提供文件；动态参数如果对象是 Docker 容器的话一般用 label，本地服务的话应该只能用文件了。</p><p>有个可能会混淆的点是有些部分名称是自定义的，但省事的话可能会和预定义的名称混起来，文中尽量用一些不太可能是关键字的来代替，例如 <code>my-router</code> 之类。以及（特别是 label 形式定义的）组件常常看起来缺少一个“定义”的过程，例如 <code>traefik.http.routers.www-router.rule=Host(xxx)</code>  中的 <code>www-router</code> 不需要提前定义，直接就能指定它的 Host。</p><h2 id="Static"><a href="#Static" class="headerlink" title="Static"></a>Static</h2><p>命令行参数或文件（YAML &#x2F; TOML）或环境变量</p><h3 id="Entrypoints"><a href="#Entrypoints" class="headerlink" title="Entrypoints"></a>Entrypoints</h3><blockquote><p><a href="https://doc.traefik.io/traefik/routing/entrypoints/">https://doc.traefik.io/traefik/routing/entrypoints/</a></p></blockquote><p>开 80 443 且 80 转 443 的例子（<code>web</code> 和 <code>websecure</code> 都是自定义的名字，不过在这边可能有点约定俗成）</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">entryPoints:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">        <span class="attr">address:</span> <span class="string">&quot;:80&quot;</span>  <span class="comment"># [host]:port[/tcp|/udp]</span></span><br><span class="line">        <span class="attr">http:</span></span><br><span class="line">            <span class="attr">redirections:</span></span><br><span class="line">                <span class="attr">entryPoint:</span></span><br><span class="line">                    <span class="attr">to:</span> <span class="string">websecure</span></span><br><span class="line">                    <span class="attr">scheme:</span> <span class="string">https</span></span><br><span class="line">    <span class="attr">websecure:</span></span><br><span class="line">        <span class="attr">address:</span> <span class="string">&quot;:443&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Providers"><a href="#Providers" class="headerlink" title="Providers"></a>Providers</h3><p>主要看看 Docker provider： <a href="https://doc.traefik.io/traefik/routing/providers/docker/">https://doc.traefik.io/traefik/routing/providers/docker/</a> （其他也都不熟 x）</p><h4 id="启用"><a href="#启用" class="headerlink" title="启用"></a>启用</h4><p>文件：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">providers:</span></span><br><span class="line">    <span class="attr">docker:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>参数里：<code>--providers.docker</code></p><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>文中例子以文件形式的配置为主（更好看清结构），对于二者转换可以参考下面例子。大体上把 YAML 的结构改成用点连接即可，具体请参考文档<br>例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Docker Provider</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line">    <span class="attr">services:</span></span><br><span class="line">        <span class="attr">my-container:</span></span><br><span class="line">            <span class="comment"># ...</span></span><br><span class="line">            <span class="attr">labels:</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.routers.www-router.rule=Host(`example-a.com`)</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.routers.www-router.service=www-service</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.services.www-service.loadbalancer.server.port=8000</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.routers.admin-router.rule=Host(`example-b.com`)</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.routers.admin-router.service=admin-service</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">traefik.http.services.admin-service.loadbalancer.server.port=9000</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># File Provider (dynamic config)</span></span><br><span class="line"><span class="attr">http:</span></span><br><span class="line">    <span class="attr">routers:</span></span><br><span class="line">        <span class="attr">www-router:</span></span><br><span class="line">            <span class="attr">rule:</span> <span class="string">&quot;Host(`example-a.com`)&quot;</span></span><br><span class="line">            <span class="attr">service:</span> <span class="string">www-service</span></span><br><span class="line">        <span class="attr">admin-router:</span></span><br><span class="line">            <span class="attr">rule:</span> <span class="string">&quot;Host(`example-b.com`)&quot;</span></span><br><span class="line">            <span class="attr">service:</span> <span class="string">admin-service</span></span><br><span class="line">    <span class="attr">services:</span></span><br><span class="line">        <span class="attr">www-service:</span></span><br><span class="line">            <span class="attr">loadBalancer:</span></span><br><span class="line">                <span class="attr">servers:</span></span><br><span class="line">                    <span class="bullet">-</span> <span class="attr">url:</span> <span class="string">&quot;http://127.0.0.1:8000&quot;</span> <span class="comment"># For example</span></span><br><span class="line">        <span class="attr">admin-service:</span></span><br><span class="line">            <span class="attr">loadBalancer:</span></span><br><span class="line">                <span class="attr">server:</span></span><br><span class="line">                    <span class="bullet">-</span> <span class="attr">url:</span> <span class="string">&quot;http://127.0.0.1:9000&quot;</span> <span class="comment"># For example</span></span><br></pre></td></tr></table></figure><p>注意这种情况（HTTP）下如果标签里只有 router，会自动生成一个 service。同时如果既有一个 router 也有一个 service 但 router 没有指定 service，则 service 会自动分配给那个 router</p><p>于是最简单的 labels 可以这样写（如果容器有开单端口甚至可以省略 port， Traefik 会自动用那个端口）</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">labels:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;traefik.http.routers.myproxy.rule=Host(`example.net`)&quot;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;traefik.http.services.myservice.loadbalancer.server.port=80&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Dynamic"><a href="#Dynamic" class="headerlink" title="Dynamic"></a>Dynamic</h2><h3 id="Routers"><a href="#Routers" class="headerlink" title="Routers"></a>Routers</h3><blockquote><p><a href="https://doc.traefik.io/traefik/routing/routers/">https://doc.traefik.io/traefik/routing/routers/</a></p></blockquote><p>主要 http routers<br>Routers 默认监听所有 Entrypoints，一般主要关注规则部分</p><p>router 需要的 rule（什么情况下用这部分配置）、middleware（中间需要什么处理）、service（给谁）、其他要求（如 TLS）<br>例子：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">http:</span></span><br><span class="line">    <span class="attr">routers:</span></span><br><span class="line">        <span class="attr">Router-1:</span></span><br><span class="line">            <span class="attr">rule:</span> <span class="string">&quot;Host(`example.com`)&quot;</span> <span class="comment"># 反引号或\&quot;来框字符串（好像是 Go 的要求）</span></span><br><span class="line">            <span class="attr">middlewares:</span> <span class="comment"># 不一定需要</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">authentication</span></span><br><span class="line">            <span class="attr">service:</span> <span class="string">&quot;service-1&quot;</span> <span class="comment"># 用 label 可以省略</span></span><br><span class="line">            <span class="attr">priority:</span> <span class="number">1</span> <span class="comment"># 不一定需要</span></span><br><span class="line">            <span class="attr">tls:</span></span><br><span class="line">                <span class="attr">certResolver:</span> <span class="string">leresolver</span> <span class="comment"># 见 tls</span></span><br></pre></td></tr></table></figure><h4 id="Rule"><a href="#Rule" class="headerlink" title="Rule"></a>Rule</h4><p>具体请看官方文档，这里给个简单例子</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">rule</span> <span class="string">=</span> <span class="string">&quot;Host(`example.com`) || (Host(`example.org`) &amp;&amp; PathPrefix(`/traefik/`))&quot;</span></span><br></pre></td></tr></table></figure><p>（PathPrefix 匹配路径前缀）</p><h4 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h4><p>每个 router 都必须有一个 service。一般来说需要预先定义 service，但例如用 Docker 的 label 就可以省略（Traefik 会自动创建并分配）</p><h3 id="Services"><a href="#Services" class="headerlink" title="Services"></a>Services</h3><p>每个 service 必有一个 loadbalancer（即使不需要负载均衡）</p><p>一般用到主要是主动指定容器内端口或者监听本地的 url。<br>主动指定端口：<code>traefik.http.services.my-service.loadbalancer.server.port=9000</code>（一行就够不依赖其他，或者说其他都自动生成了）</p><p>文件形式的例子：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">http:</span></span><br><span class="line">    <span class="attr">routers:</span></span><br><span class="line">        <span class="attr">my-router:</span></span><br><span class="line">            <span class="attr">rule:</span> <span class="string">&quot;Host(`example.com`)&quot;</span></span><br><span class="line">            <span class="attr">service:</span> <span class="string">my-service</span></span><br><span class="line">    <span class="attr">services:</span></span><br><span class="line">        <span class="attr">my-service:</span></span><br><span class="line">            <span class="attr">loadBalancer:</span></span><br><span class="line">                <span class="attr">servers:</span></span><br><span class="line">                    <span class="bullet">-</span> <span class="attr">url:</span> <span class="string">&quot;http://host.docker.internal:8080&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Middlewares"><a href="#Middlewares" class="headerlink" title="Middlewares"></a>Middlewares</h3><p>细节请看文档 <a href="https://doc.traefik.io/traefik/middlewares/overview">https://doc.traefik.io/traefik/middlewares/overview</a> 。能干挺多事，但先只说个 BasicAuth</p><h4 id="BasicAuth"><a href="#BasicAuth" class="headerlink" title="BasicAuth"></a>BasicAuth</h4><p>为一些没有认证功能的服务加个认证功能，例如 Traefik 的 Dashboard（见后文）<br>例子：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">labels:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;traefik.http.middlewares.test-auth.basicauth.users=username:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;traefik.http.routers.my-router.middlewares=test-auth&quot;</span></span><br></pre></td></tr></table></figure><p>格式是“用户名:处理过的密码”，处理过密码部分需要是实际密码 MD5 &#x2F; SHA1 &#x2F; BCrypt 后的结果，如果有 <code>$</code> 的话要换成 <code>$$</code><br>密码生成可以用 htpasswd，或者熟悉 Python（Python 3）的话：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install bcrypt</span><br><span class="line">python -c <span class="string">&quot;import bcrypt;print(bcrypt.hashpw(&#x27;YOUR_PASSWORD&#x27;.encode(&#x27;UTF-8&#x27;),bcrypt.gensalt()).decode(&#x27;UTF-8&#x27;).replace(&#x27;$&#x27;,&#x27;\$\$&#x27;))&quot;</span></span><br></pre></td></tr></table></figure><p>如果用 Windows PowerShell 需要把最后的 <code>\$\$</code> 中的 \ 换成 ` </p><h2 id="TLS"><a href="#TLS" class="headerlink" title="TLS"></a>TLS</h2><blockquote><p><a href="https://doc.traefik.io/traefik/https/overview/">https://doc.traefik.io/traefik/https/overview/</a></p></blockquote><p>理论上 TLS 相关设置既有静态也有动态，但感觉还是放在一起看比较合适</p><p>TLS 证书可以手动指定，也可以让 Traefik 自动申请（是 Let’s Encrypt 的）。Traefik 可以全自动完成证书申请和续期（主要看看这个，手动指定请参考文档）</p><p>首先要在静态配置中定义 certificatesResolvers，之后每个 router 通过 tls.certresolver 选一个用。Traefik 会自动通过对应 rule 找域名申请对应证书（也可以通过 tls.domains 指定）</p><p>例子（结合起来看）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#Static configuration </span></span><br><span class="line"><span class="attr">certificatesResolvers:</span></span><br><span class="line">    <span class="attr">my-resolver:</span></span><br><span class="line">        <span class="attr">acme:</span></span><br><span class="line">            <span class="attr">email:</span> <span class="string">&quot;name@example.com&quot;</span> <span class="comment"># Let&#x27;s Encrypt 注册邮箱</span></span><br><span class="line">            <span class="attr">storage:</span> <span class="string">&quot;acme.json&quot;</span> <span class="comment"># 容器外映射进来（xxx/acme.json:/acme.json），权限 600</span></span><br><span class="line">            <span class="attr">httpChallenge:</span> <span class="comment"># 不同申请方法见下文</span></span><br><span class="line">                <span class="attr">entryPoint:</span> <span class="string">web</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Dynamic configuration</span></span><br><span class="line"><span class="attr">labels:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">traefik.http.routers.my-router.rule=Host(`example.com`)</span> <span class="string">&amp;&amp;</span> <span class="string">Path(`/example`)</span></span><br><span class="line"><span class="comment"># - traefik.http.routers.blog.tls=true</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">traefik.http.routers.my-router.tls.certresolver=my-resolver</span></span><br></pre></td></tr></table></figure><p>也可以针对 Entrypoints 配置默认的 certResolver</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">entryPoints:</span></span><br><span class="line">    <span class="attr">websecure:</span></span><br><span class="line">    <span class="attr">address:</span> <span class="string">&#x27;:443&#x27;</span> </span><br><span class="line">    <span class="attr">http:</span> </span><br><span class="line">        <span class="attr">tls:</span> </span><br><span class="line">            <span class="attr">certResolver:</span> <span class="string">leresolver</span></span><br></pre></td></tr></table></figure><h3 id="ACME-Challenges"><a href="#ACME-Challenges" class="headerlink" title="ACME Challenges"></a>ACME Challenges</h3><p>证书申请需要 Challenges 验证域名真正指向了服务器，有几种可选的验证方式</p><h4 id="tlsChallenge"><a href="#tlsChallenge" class="headerlink" title="tlsChallenge"></a>tlsChallenge</h4><p>443 开就行。但注意<a href="https://community.letsencrypt.org/t/cannot-negotiate-alpn-protocol/111996/2">套 Cloudflare 时这种方式无效</a>（看 CDN 是否允许 non-HTTP ALPNs），可以暂时关掉 CDN 只保留 DNS 解析（并不推荐）或者换其他方法（套 Cloudflare 了可以考虑下文的 DNS challenge）</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">certificatesResolvers:</span></span><br><span class="line">    <span class="attr">myresolver:</span></span><br><span class="line">        <span class="attr">acme:</span></span><br><span class="line">            <span class="comment"># ...</span></span><br><span class="line">            <span class="attr">tlsChallenge:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><h4 id="httpChallenge"><a href="#httpChallenge" class="headerlink" title="httpChallenge"></a>httpChallenge</h4><p>需要开 80 口</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">certificatesResolvers:</span></span><br><span class="line">    <span class="attr">myresolver:</span></span><br><span class="line">        <span class="attr">acme:</span></span><br><span class="line">            <span class="comment"># ...</span></span><br><span class="line">            <span class="attr">httpChallenge:</span></span><br><span class="line">                <span class="attr">entrypoint:</span> <span class="string">web</span> <span class="comment"># 对应 80 口的那个</span></span><br></pre></td></tr></table></figure><h4 id="dnsChallenge"><a href="#dnsChallenge" class="headerlink" title="dnsChallenge"></a>dnsChallenge</h4><p>参考 <a href="https://doc.traefik.io/traefik/https/acme/#dnschallenge">https://doc.traefik.io/traefik/https/acme/#dnschallenge</a><br>只有这种方式能申请通配符证书，具体配置看所使用的 DNS 提供商</p><p>Cloudflare 的话建议去 My Profile → API Tokens → Create Token 创建一个低权限的 Token，根据<a href="https://github.com/traefik/traefik/issues/5965">这个 issue</a>，需要的权限是“Zone - Zone - Read”和“Zon - DNS - Edit”，至于具体 Zone 的选择就看实际情况了<br>之后在 Docker 的环境变量中增加“CF_DNS_API_TOKEN&#x3D;XXXXXXX”即可</p><h1 id="Extra"><a href="#Extra" class="headerlink" title="Extra"></a>Extra</h1><h2 id="Network-相关"><a href="#Network-相关" class="headerlink" title="Network 相关"></a>Network 相关</h2><p>Docker compose 默认会创建单独 network，如果一起用默认的 bridge 的话要加上 <code>network_mode: bridge</code>。或者其他方式但总之 Traefik 得和目标容器有在相同 network 中。</p><h2 id="Docker-内的-Traefik-访问-Docker-外的本地服务"><a href="#Docker-内的-Traefik-访问-Docker-外的本地服务" class="headerlink" title="Docker 内的 Traefik 访问 Docker 外的本地服务"></a>Docker 内的 Traefik 访问 Docker 外的本地服务</h2><p>虽然以 Docker 为主，但总得有这个能力 x。这里的方法不止适用于 Traefik，其他情况下容器内访问容器外本地也可以这么做<br>需要的设置：</p><ul><li>docker run 添加参数：<code>--add-host=host.docker.internal:host-gateway</code>（docker 版本 20+）</li><li>docker compose 在对应 container 下面增加  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">extra_hosts:</span><br><span class="line">    - &quot;host.docker.internal:host-gateway&quot;</span><br></pre></td></tr></table></figure>例如 <code>localhost:1234</code> 的服务，设置后 Docker 内访问则用 <code>host.docker.internal:1234</code> 即可</li></ul><p>参考： <a href="https://stackoverflow.com/questions/24319662">https://stackoverflow.com/questions/24319662</a></p><h2 id="Dashboard"><a href="#Dashboard" class="headerlink" title="Dashboard"></a>Dashboard</h2><p><a href="https://doc.traefik.io/traefik/operations/dashboard/">https://doc.traefik.io/traefik/operations/dashboard/</a><br>不一定有必要，但 <del>看着好看</del> 有时 debug 相对方便<br>给 Traefik 自己上 label 就行</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 改自官方的例子</span></span><br><span class="line"><span class="comment"># Dynamic Configuration</span></span><br><span class="line"><span class="attr">labels:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&quot;traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)&quot;</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&quot;traefik.http.routers.dashboard.service=api@internal&quot;</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&quot;traefik.http.routers.dashboard.middlewares=auth&quot;</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&quot;traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0&quot;</span></span><br></pre></td></tr></table></figure><p>（发现上文没写 middleware，可能有空加上）<br>其中 rule 部分要包含 <code>/api</code> 和 <code>/dashboard</code> （这里直接整个域名肯定包含了），service 是固定的 <code>api@internal</code>。虽然 dashboard 只能看看但最好还是上个验证，例如 <code>basicauth</code> &#96;</p><p>之后访问 <code>https://traefik.example.com/dashboard/</code> 即可，注意默认情况下最后的那个 <code>/</code> 不可省略</p><h2 id="关于端口"><a href="#关于端口" class="headerlink" title="关于端口"></a>关于端口</h2><p>不少 web 服务都是直接 <code>-p xxxx:xxxx</code>，个人感觉不太建议，毕竟除非容器内服务配置了只监听部分 IP 或 IP 段，这样一般会监听所有 ip，可能会被扫出来。测试的话可以 <code>-p 127.0.0.1:xxxx:xxxx</code> 加 SSH 的本地端口转发或者其他方法，如果比较简单感觉 Traefik 直接用指定端口就行</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>参考过的文章，也许它们比本文更合适</p><ul><li><a href="https://traefik.io/blog/traefik-2-0-docker-101-fc2893944b9d/">Traefik Proxy 2.x and Docker 101</a></li><li><a href="https://kwojcicki.github.io/blog/TRAEFIK">Understanding Traefik</a></li><li><a href="https://docs.portainer.io/advanced/reverse-proxy/traefik">Deploying Portainer behind Traefik Proxy</a></li><li><a href="https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/">https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;前阵子迁移服务器，想着 Docker 容器挺多的，就试着把反代从 Nginx 换成了 Traefik。虽然主要只用上了比较基础的部分，但感觉挺不错的。Traefik 的一大特点就是对于一堆 Docker 容器，容器配置在自己 label 那儿（简单点一两个 label 就够</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>部署 Matrix 服务器（Dendrite）</title>
    <link href="https://skybirds.sbs/2023/Dendrite/"/>
    <id>https://skybirds.sbs/2023/Dendrite/</id>
    <published>2023-01-26T08:53:55.000Z</published>
    <updated>2023-07-09T14:41:01.056Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><a href="https://matrix.org/docs/projects/server/dendrite">Dendrite</a> is a second-generation <a href="https://matrix.org/">Matrix</a> homeserver written in Go. It intends to provide an efficient, reliable and scalable alternative to <a href="https://matrix.org/docs/projects/server/synapse">Synapse</a></p></blockquote><p>挺早了解了 Matrix，但听说 Synapse 占用较高，一般服务器不太跑得动，于是对 Matrix 敬而远之 x。最近与另一位先生交流了才知道 Dendrite 占用已经可以非常低了，于是也试着部署了一个。</p><p>我的部署方式是 Docker compose，Monolith mode + PostgrSQL，关于 mode 区别和数据库选择请参考<a href="https://matrix-org.github.io/dendrite/installation/planning">官方说明</a> 。以及官方的配置要求看着挺高，但小规模使用的话没啥参考价值 x，自己的 Dendrite 占用才 60MB+（PostgreSQL 也大约 60MB+）。</p><h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><p><a href="https://github.com/matrix-org/dendrite/blob/main/dendrite-sample.monolith.yaml">主配置文件 dendrite-sample.monolith</a><br><a href="https://github.com/matrix-org/dendrite/tree/main/build/docker">Docker 相关文件</a></p><p>文件结构（需要设置的部分，并非最终结果）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">dendrite</span><br><span class="line">├── config</span><br><span class="line">│   ├── dendrite.yaml</span><br><span class="line">│   ├── matrix_key.pem</span><br><span class="line">│   ├── server.crt</span><br><span class="line">│   └── server.key</span><br><span class="line">├── docker-compose.yml</span><br><span class="line">└── postgres</span><br><span class="line">    └── create_db.sh</span><br></pre></td></tr></table></figure><p>下文中主域名就用 <code>example.com</code> 代替了。</p><h2 id="dendrite-yaml"><a href="#dendrite-yaml" class="headerlink" title="dendrite.yaml"></a>dendrite.yaml</h2><ul><li><code>global:servername</code><ul><li><code>example.com</code></li></ul></li><li><code>global:database:connection_string</code><ul><li><code>username</code>、<code>password</code> 和 docker-compose.yaml 里的保持相同</li><li><code>hostname</code> 换成 <code>postgres</code> （Docker 下不额外配置的话主机名好像是跟着容器名…？）</li></ul></li><li><code>global:well-known-*-name</code><ul><li><code>example.com</code></li></ul></li><li><code>media_api:basic_path</code><ul><li>好像默认的和 docker-compose.yml 中的不同，我把这里的改成了 <code>/var/dendrite/media</code>（对应 docker-compose.yml 第 35 行）</li></ul></li></ul><h2 id="docker-compose-yml"><a href="#docker-compose-yml" class="headerlink" title="docker-compose.yml"></a>docker-compose.yml</h2><ul><li><code>services:postgres:volums</code><ul><li>参考注释修改第二条，把 <code>./path_to/postgresql</code> 改改（<del>其实不改也不是不行</del>）</li></ul></li><li><code>services:postgres:environment</code><ul><li>用户名密码，和 dendrite.yaml 中的保持相同</li></ul></li></ul><h2 id="matrix-key-pem"><a href="#matrix-key-pem" class="headerlink" title="matrix_key.pem"></a>matrix_key.pem</h2><p>参考官方给的命令，在 config 目录下执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> --entrypoint=<span class="string">&quot;&quot;</span> \</span><br><span class="line">  -v $(<span class="built_in">pwd</span>):/mnt \</span><br><span class="line">  matrixdotorg/dendrite-monolith:latest \</span><br><span class="line">  /usr/bin/generate-keys \</span><br><span class="line">  -private-key /mnt/matrix_key.pem \</span><br></pre></td></tr></table></figure><p>这里只需要生成 <code>matrix_key.pem</code>。以及可以随时执行，Docker 会自动拉取镜像。</p><h2 id="server-crt-amp-server-key"><a href="#server-crt-amp-server-key" class="headerlink" title="server. crt &amp; server.key"></a>server. crt &amp; server.key</h2><p>我使用 <a href="https://acme.sh/">acme.sh</a> 来自动配置：</p><ol><li>默认使用 Let’s Encrypt（可选）：<code>acme.sh --set-default-ca --server letsencrypt</code></li><li>申请证书：<code>acme.sh --issue -d matrix.example.com</code> （根据实际情况不同，可能需要临时开个 80 口，具体请看 acme.sh 的说明并选择申请方式）</li><li>安装证书：<code>acme.sh --install-cert -d matrix.example.com --key-file path_to_dendrite/config/server.key --fullchain-file path_to_dendrite/config/server.crt --reloadcmd &quot;docker restart dendrite-monolith-1&quot;</code> （根据实际替换 <code>path_to_dendrite</code> 和 <code>dendrite-monolith-1</code>）<br>之后续期啥的留给 acme.sh 就行了。</li></ol><h2 id="postgres-x2F-create-db-sh"><a href="#postgres-x2F-create-db-sh" class="headerlink" title="postgres&#x2F;create_db.sh"></a>postgres&#x2F;create_db.sh</h2><p><a href="https://github.com/matrix-org/dendrite/blob/main/build/docker/postgres/create_db.sh">https://github.com/matrix-org/dendrite/blob/main/build/docker/postgres/create_db.sh</a><br>（不需要修改）</p><h2 id="Delegation"><a href="#Delegation" class="headerlink" title="Delegation"></a>Delegation</h2><blockquote><p><a href="https://matrix-org.github.io/dendrite/installation/domainname#delegation">https://matrix-org.github.io/dendrite/installation/domainname#delegation</a></p></blockquote><p>简单说大致就是使用 <code>matrix.example.com</code> 定位实际服务器、与服务器交流，但用户名等部分仍然使用 <code>example.com</code>。比较建议这种方式，既保持了用户名的简洁也方便服务器迁移啥的。<br>具体设置部分需要注意的就是（Well-known delegation）</p><ol><li>在 <code>dendrite.yaml</code> 的 <code>server_name</code> 部分使用 <code>example.com</code></li><li>证书（<code>server.crt</code> 和 <code>server.key</code>）用 <code>matrix.example.com</code></li><li>访问 <code>example.com/.well-known/matrix/server</code> 和 <code>example.com/.well-known/matrix/client</code> 时返回特定 json 指向 <code>matrix.example.com</code><blockquote><p>第 3 步可以通过 SRV 记录实现（DNS SRV delegation），不过官方说不太推荐。似乎也只能用于服务器见的发现。</p></blockquote></li></ol><h3 id="Cloudflare-Workers-实现-Well-known-delegation"><a href="#Cloudflare-Workers-实现-Well-known-delegation" class="headerlink" title="Cloudflare Workers 实现 Well-known delegation"></a>Cloudflare Workers 实现 Well-known delegation</h3><p>要实现 Delegation 实际上额外需求只有在客户端请求 <code>example.com/.well-known/matrix/*</code> 时返回简单 json。官方文档中是使用 Caddy&#x2F;Nginx，但不考虑反代的话单独为了这个架个 Web 服务器感觉没啥必要 x，于是就丢给 Cf Workers 了。</p><h4 id="Workers-参考代码："><a href="#Workers-参考代码：" class="headerlink" title="Workers 参考代码："></a>Workers 参考代码：</h4><blockquote><p>参考<del>（主要抄自）</del>： <a href="https://helderferreira.io/matrix-well-known-with-cloudflare/">https://helderferreira.io/matrix-well-known-with-cloudflare/</a> （有所修改）</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">addEventListener</span>(<span class="string">&quot;fetch&quot;</span>, <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  event.<span class="title function_">respondWith</span>(</span><br><span class="line">    <span class="title function_">handleRequest</span>(event.<span class="property">request</span>).<span class="title function_">catch</span>(</span><br><span class="line">      <span class="function">(<span class="params">err</span>) =&gt;</span> <span class="keyword">new</span> <span class="title class_">Response</span>(err.<span class="property">stack</span>, &#123; <span class="attr">status</span>: <span class="number">500</span> &#125;)</span><br><span class="line">    )</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handleRequest</span>(<span class="params">request</span>)&#123;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> url = <span class="keyword">new</span> <span class="title function_">URL</span>(request.<span class="property">url</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> headers = &#123;</span><br><span class="line">    <span class="attr">headers</span>: &#123;</span><br><span class="line">      <span class="string">&quot;content-type&quot;</span>: <span class="string">&quot;application/json;charset=UTF-8&quot;</span>,</span><br><span class="line">      <span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>: <span class="string">&#x27;*&#x27;</span>,  <span class="comment">//解决可能会遇到跨域问题</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> serverJson = &#123;</span><br><span class="line">    <span class="string">&quot;m.server&quot;</span>: <span class="string">&quot;matrix.example.com:8448&quot;</span> </span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> clientJson = &#123;</span><br><span class="line">      <span class="string">&quot;m.homeserver&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;base_url&quot;</span>: <span class="string">&quot;https://matrix.example.com:8448&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> msg</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (url.<span class="property">pathname</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;server&quot;</span>)) &#123;</span><br><span class="line">    msg  = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(serverJson)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (url.<span class="property">pathname</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;client&quot;</span>)) &#123;</span><br><span class="line">    msg = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(clientJson)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (msg) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>( msg , headers);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&#x27;Not Found.&#x27;</span>, &#123; <span class="attr">status</span>: <span class="number">404</span> &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Route-设置"><a href="#Route-设置" class="headerlink" title="Route 设置"></a>Route 设置</h4><p>Route：<code>example.com/.well-known/matrix/*</code></p><h1 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h1><h2 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h2><p>在 docker-compose.yml 所在目录执行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose start</span><br></pre></td></tr></table></figure><blockquote><p>较旧 Docker 可能要把 <code>docker compose</code> 换成 <code>docker-compose</code></p></blockquote><p>只是测试的话可以：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up</span><br></pre></td></tr></table></figure><p>这样可以实时查看那两个容器的输出，<code>Ctrl + C</code> 停止。</p><h2 id="停止"><a href="#停止" class="headerlink" title="停止"></a>停止</h2><p>在 docker-compose.yml 所在目录执行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose stop</span><br></pre></td></tr></table></figure><h2 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker logs dendrite-monolith-1 -n [lines]</span><br><span class="line"><span class="comment"># -n 指定行数，从末尾算起</span></span><br></pre></td></tr></table></figure><h1 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h1><p>可以使用 <a href="https://federationtester.matrix.org/">Federation Tester</a> 测试 federation 情况。客户端的话直接用客户端测试即可（记得开注册）。以及 Element Desktop 调试快捷键是 <code>Ctrl + Shift + I</code>。</p><h1 id="帐号管理"><a href="#帐号管理" class="headerlink" title="帐号管理"></a>帐号管理</h1><p>自己由于不开放注册，也没几个号，于是临时开放注册然后手动关闭 x。具体请参考<a href="https://matrix-org.github.io/dendrite/administration">官方说明</a>。</p><h1 id="与-XMPP-对比"><a href="#与-XMPP-对比" class="headerlink" title="与 XMPP 对比"></a>与 XMPP 对比</h1><p>其实都没有很深入的了解，XMPP 服务端（<a href="https://prosody.im/">Prosody</a>）的部署时间也比较早了，应该有所遗忘。这里只是简单比较一下个人的感受。</p><ul><li>服务端<br>  Prosody 和 Dendrite 对比的话，感觉 Dendrite 部署难度上要低于 Prosody，不过占用略高于 Prosody（假如 PostgreSQL 也算上的话内存占用至少是 Prosody 的两倍了）。我的 Prosody 部署的比较早，当时连实现上传文件这一功能都需要专门加个模组，不过较新的 0.12 好像内置了。</li><li>客户端<br>  Matrix 基础功能感觉比 XMPP 完善多了，客户端完成度也高于 XMPP。虽然 Element 客户端基于 Electron，但至少能保证多平台都有相似的用户体验，也有很多其他客户端可以选择。XMPP 客户端体验感觉参差不齐，也还要看对 XEP 的支持程度。虽然基础功能肯定有所保证，但其他功能得客户端全都支持才能真正用上。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://matrix.org/docs/projects/server/dendrite&quot;&gt;Dendrite&lt;/a&gt; is a second-generation &lt;a href=&quot;https://matrix.org/&quot;</summary>
      
    
    
    
    
    <category term="matrix" scheme="https://skybirds.sbs/tags/matrix/"/>
    
  </entry>
  
  <entry>
    <title>Hello...world</title>
    <link href="https://skybirds.sbs/2022/Hello...world/"/>
    <id>https://skybirds.sbs/2022/Hello...world/</id>
    <published>2022-12-29T10:06:13.000Z</published>
    <updated>2023-07-09T14:41:01.057Z</updated>
    
    <content type="html"><![CDATA[<p>开始搭这玩意儿的初衷是…想着在主域名下放点东西 x，然后想起来静态网页完全可以一条龙丢 Github 上，刚好看到个感觉还行的 Hexo 主题，于是就浪费了一些时间部署了。虽然其实细节上感觉不够行，不过考虑到自己几乎没有相关知识，就先这样放着了 x。</p><p>至于内容…本没有写的打算，但想想毕竟有时折腾些没用的东西，满记录下应该也不错。要是能对别人或多或少有点帮助就更好了。虽然应该不太可能。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;开始搭这玩意儿的初衷是…想着在主域名下放点东西 x，然后想起来静态网页完全可以一条龙丢 Github 上，刚好看到个感觉还行的 Hexo 主题，于是就浪费了一些时间部署了。虽然其实细节上感觉不够行，不过考虑到自己几乎没有相关知识，就先这样放着了 x。&lt;/p&gt;
&lt;p&gt;至于内容</summary>
      
    
    
    
    
  </entry>
  
</feed>
