<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/css/pretty-feed-v3.xsl" type="text/xsl"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>昔我往矣</title>
    <link>https://xnow.me</link>
    <description>昔我往矣，杨柳依依；今我来思，雨雪霏霏</description>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <image>
      <url>/static/image/favicon2.png</url>
      <title>昔我往矣</title>
      <link>https://xnow.me</link>
    </image>
    <lastBuildDate>Sat, 04 Apr 2026 22:45:55 +0000</lastBuildDate>
    <item>
      <title>uv 实战：Python 包构建与分发</title>
      <link>http://xnow.me/programs/python-uv-build-publish.html</link>
      <description>&lt;p&gt;从使用方式上区分，有两种Python项目，一种是应用程序，面向最终用户；一种是Python库项目，可以供其他项目使用，比如常用的requests、Flask、SQLAlchemy等库。本文探讨下uv如何支持Python的库开发与分发。&lt;/p&gt;
&lt;p&gt;&lt;img alt="sping-mountain" src="/usr/uploads/2026/03/1773588691.7366402.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;层层春山如黛。又是一个春天到了。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <content:encoded>&lt;p&gt;从使用方式上区分，有两种Python项目，一种是应用程序，面向最终用户；一种是Python库项目，可以供其他项目使用，比如常用的requests、Flask、SQLAlchemy等库。本文探讨下uv如何支持Python的库开发与分发。&lt;/p&gt;
&lt;p&gt;&lt;img alt="sping-mountain" src="/usr/uploads/2026/03/1773588691.7366402.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;层层春山如黛。又是一个春天到了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!--more--&gt;

&lt;p&gt;Python 的包管理机制随着Python的发展经历了几个阶段，逐渐发展成熟：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;史前时代(1998 - 2000年)： 开发者编写一个 &lt;code&gt;setup.py&lt;/code&gt; 文件，用户下载源码后通过 &lt;code&gt;python setup.py install&lt;/code&gt; 进行安装。用户需要手动下载和安装依赖。&lt;/li&gt;
&lt;li&gt;变革时代：Setuptools 与 PyPI（2004年左右）：&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/"&gt;PyPI (Python Package Index)&lt;/a&gt; 诞生，成为了 Python 包的中央仓库。&lt;code&gt;easy_install&lt;/code&gt; 可以自动从PyPI下载和安装依赖（但不支持卸载）。&lt;/li&gt;
&lt;li&gt;引入&lt;code&gt;setuptools&lt;/code&gt;工具和  &lt;code&gt;Egg&lt;/code&gt; 格式，将代码和元数据（如版本、依赖、入口点）打包在一起。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;经典时代，Pip 与 Wheel (2008 - 2014年)：&lt;ul&gt;
&lt;li&gt;pip 取代了 easy_install，引入了卸载功能和 requirements.txt 文件管理依赖，很长一段时间这都是最流行的方法。&lt;/li&gt;
&lt;li&gt;Wheel 格式 (.whl) 诞生（2012年）：它取代了 Egg 格式。Wheel 是预编译的二进制格式，安装速度极快，解决了“安装一个包得先装一堆编译器”的痛苦。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;现代化（2018年至今）：&lt;code&gt;PEP 517 &amp;amp; PEP 518&lt;/code&gt; 定义了打包的标准接口和构建时依赖问题，降低了对 &lt;code&gt;setuptools&lt;/code&gt; 的强依赖。基于新的标准，Poetry、uv和pdm等开发管理工具提供了各自可用的构建后端，比如&lt;code&gt;poetry-core&lt;/code&gt;、&lt;code&gt;uv-build&lt;/code&gt; 和&lt;code&gt;pdm-backend&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在 PEP 517 和 PEP 518 确立的现代标准下，Python 的打包流程被解耦为前端和后端。前端负责与用户交互，后端负责生成最终的产品（Wheel 或源码包）。在 pyproject.toml中可以声明项目使用的后端。项目的后端工具不一定要和前端匹配，比如uv也可以搭配 &lt;code&gt;poetry-core&lt;/code&gt; 后端。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文将基于uv创建一个Python包，并将其上传到自建的PyPI。演示完整的Python包开发、分发和使用的生命周期。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;版本信息：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OS： Debian 13（Trixie）&lt;/li&gt;
&lt;li&gt;Python：3.13&lt;/li&gt;
&lt;li&gt;uv：0.10.7&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="universal-add"&gt;universal-add 包开发&lt;/h2&gt;
&lt;p&gt;本文将开发 &lt;code&gt;universal-add&lt;/code&gt;包，使其支持各类数据结构的加法操作。&lt;/p&gt;
&lt;h3 id="_1"&gt;创建项目&lt;/h3&gt;
&lt;p&gt;使用 uv 创建项目 &lt;code&gt;universal-add&lt;/code&gt;， 这个项目支持 &lt;code&gt;int&lt;/code&gt;、&lt;code&gt;float&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;set&lt;/code&gt;、&lt;code&gt;dict&lt;/code&gt;等数据类型的加法。该项目仅仅作为演示，其中的逻辑并不重要，也不是真实存在的项目。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;gt;=3.10&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;universal-add&lt;span class="w"&gt;   &lt;/span&gt;--package&lt;span class="w"&gt; &lt;/span&gt;--description&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;an universal python add package&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;指定了项目支持 &lt;code&gt;python 3.10&lt;/code&gt; 及以上版本，使用包格式（src布局）初始化项目，并为项目设置一个描述。&lt;/p&gt;
&lt;p&gt;以下是生成的 pyproject.toml 文件&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;universal-add&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;an universal python add package&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;readme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;README.md&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xnow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xnow@xnow.me&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;requires-python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;gt;=3.10&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;[project.scripts]&lt;/span&gt;
&lt;span class="n"&gt;uadd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;universal_add:main&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 手动将 universal-add 修改为 uadd&lt;/span&gt;

&lt;span class="k"&gt;[build-system]&lt;/span&gt;
&lt;span class="n"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uv_build&amp;gt;=0.10.7,&amp;lt;0.11.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;build-backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;uv_build&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;pyproject.toml 文件的大部分内容我们都很熟悉了，有两项新增的配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[project.scripts]&lt;/code&gt;，指定&lt;code&gt;uadd&lt;/code&gt;命令的入口函数为 &lt;code&gt;universal_add&lt;/code&gt;包中的&lt;code&gt;main()&lt;/code&gt;函数。把默认的 &lt;code&gt;universal-add&lt;/code&gt; 修改为 &lt;code&gt;uadd&lt;/code&gt;，安装后这个包的命令就变为了 &lt;code&gt;uadd&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[build-system]&lt;/code&gt; 就是uv指定的构建后端，这里使用了uv默认的后端 &lt;code&gt;uv_build&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="_2"&gt;项目代码&lt;/h3&gt;
&lt;p&gt;进入项目目录，修改 &lt;code&gt;src/universal_add/__init__.py&lt;/code&gt;  为如下内容：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ast&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;click&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;typing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeAlias&lt;/span&gt;

&lt;span class="c1"&gt;# Define type aliases for supported types&lt;/span&gt;
&lt;span class="n"&gt;Addable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TypeAlias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypeVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;T&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;IncompatibleTypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Raised when two types cannot be added or merged according to the defined logic.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Universal addition core logic.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;IncompatibleTypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cannot add &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;smart_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Convert CLI string input into Python objects safely.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;literal_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Fallback to raw string if it&amp;#39;s not a valid Python literal&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;


&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Universal Add CLI: A tool to add/merge numbers, strings, lists, and dicts.&lt;/span&gt;

&lt;span class="sd"&gt;    Example:&lt;/span&gt;
&lt;span class="sd"&gt;      uadd 1 2&lt;/span&gt;
&lt;span class="sd"&gt;      uadd &amp;quot;[1,2]&amp;quot; &amp;quot;[3,4]&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;      uadd &amp;quot;{&amp;#39;key&amp;#39;: &amp;#39;val&amp;#39;}&amp;quot; &amp;quot;{&amp;#39;new&amp;#39;: 1}&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Parse inputs&lt;/span&gt;
        &lt;span class="n"&gt;val_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smart_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;val_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smart_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Execute addition&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Output result&lt;/span&gt;
        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;IncompatibleTypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secho&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secho&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unexpected Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;本项目的&lt;code&gt;main()&lt;/code&gt;函数使用&lt;code&gt;click&lt;/code&gt;接收和处理用户传入的参数，用户传入。安装click依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;click
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;项目名是 universal-add ，通过 &lt;code&gt;uv tool install&lt;/code&gt;可以发布到全局uv 工具中，后续可以方便执行。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--editable&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# --editable 模式，会同步项目代码的更改&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;
universal-add&lt;span class="w"&gt; &lt;/span&gt;v0.1.0
-&lt;span class="w"&gt; &lt;/span&gt;uadd

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;uadd&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="m"&gt;300&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;uadd&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;b
ab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;跟踪下底层逻辑：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;uadd&lt;/span&gt;
&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uadd&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;realpath&lt;/span&gt; &lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uadd&lt;/span&gt;
&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;universal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uadd&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;universal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uadd&lt;/span&gt;
&lt;span class="c1"&gt;#!/home/xnow/.local/share/uv/tools/universal-add/bin/python3&lt;/span&gt;
&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;universal_add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-script.pyw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.exe&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看相应的 &lt;code&gt;.pth&lt;/code&gt; 文件，指向了 universal-add 的开发目录。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;~/.local/share/uv/tools/universal-add/lib/python3.13/site-packages/universal_add.pth
/tmp/universal-add/src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;.pth&lt;/code&gt; 文件提供了一种声明式的方法： 只要在 site-packages 里放一个 &lt;code&gt;&amp;lt;project name&amp;gt;.pth&lt;/code&gt;，里面写上项目的绝对路径，Python 就能像访问内置库一样访问你的项目。&lt;/p&gt;
&lt;p&gt;好的，项目到这里就开发好了，接下来部署 PyPI 服务。&lt;/p&gt;
&lt;h2 id="pypi"&gt;PyPI 部署和使用&lt;/h2&gt;
&lt;p&gt;Python官方中央仓库地址为 &lt;a href="https://pypi.org"&gt;pypi.org&lt;/a&gt;，上传的地址为：https://upload.pypi.org/legacy/ 。
Python也提供了供练习使用的测试版本中央仓库，地址为 &lt;a href="https://test.pypi.org"&gt;test.pypi.org&lt;/a&gt;，上传地址为 https://test.pypi.org/legacy/ 。&lt;/p&gt;
&lt;p&gt;本文使用自建 PyPI 的方法测试Python包上传和下载使用。自建PyPI有多种方式，比较靠谱的是使用 &lt;a href="https://www.sonatype.com/products/nexus-community-edition-download"&gt;Sonatype Nexus Repository&lt;/a&gt; 之类的成熟软件，可以host自己编写的包，也可以代理官方的PyPI。为了测试方便，我们使用 pypiserver 部署简易轻量PyPI，当前版本为 v2.4.1。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pypiserver&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;passlib&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;--with bcrypt==4.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这里的passlib是加密库，要给pypi设置密码就需要这个库。 在bcrypt做密码加密的情况下，pypiserver需要安装对应的bcrypt库，使用 &lt;code&gt;--with bcrypt==4.0.1&lt;/code&gt;  参数指定。&lt;/p&gt;
&lt;p&gt;创建密码文件 password.txt，使用bcrypt 算法加密，用户名为 xnow，密码为 &lt;code&gt;xnowXNOWxnow&lt;/code&gt;。如果bcrypt依赖不存在，先pip安装下。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;import bcrypt; user=&amp;quot;xnow&amp;quot;;passwd=&amp;quot;xnowXNOWxnow&amp;quot;; print(&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot; % (user,bcrypt.hashpw(passwd.encode(), bcrypt.gensalt()).decode()))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;启动 pypiserver ，监听到 8082 端口上：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;pypi_data
$&lt;span class="w"&gt; &lt;/span&gt;pypi-server&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;password.txt&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8082&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pypi_data/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;启动免密 pypiserver的方法是 &lt;code&gt;pypi-server -p 8082 pypi_data/ -a . -P .&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="universal-add_1"&gt;上传 universal-add 项目&lt;/h3&gt;
&lt;p&gt;进入到 universal-add 项目目录，执行构建和发布操作&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;build
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;dist/
universal_add-0.1.0-py3-none-any.whl&lt;span class="w"&gt;  &lt;/span&gt;universal_add-0.1.0.tar.gz

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;publish&lt;span class="w"&gt; &lt;/span&gt;--publish-url&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:8082
Publishing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:1888/
Enter&lt;span class="w"&gt; &lt;/span&gt;username&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__token__&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;token&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;xnow
Enter&lt;span class="w"&gt; &lt;/span&gt;password:&lt;span class="w"&gt; &lt;/span&gt;
Uploading&lt;span class="w"&gt; &lt;/span&gt;universal_add-0.1.0.tar.gz&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.4KiB&lt;span class="o"&gt;)&lt;/span&gt;
Uploading&lt;span class="w"&gt; &lt;/span&gt;universal_add-0.1.0-py3-none-any.whl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.4KiB&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;先使用 &lt;code&gt;uv build&lt;/code&gt; 构建，会在项目的 &lt;code&gt;dist/&lt;/code&gt; 目录下创建 &lt;code&gt;.whl&lt;/code&gt;和&lt;code&gt;.tar.gz&lt;/code&gt; 文件。运行 &lt;code&gt;uv publish ...&lt;/code&gt; 命令就是上传这两个文件到 PyPI上。&lt;/p&gt;
&lt;p&gt;然后使用 &lt;code&gt;uv publish&lt;/code&gt; 上传到PyPI。按提示输入用户名和密码即可。 整个过程几乎不用关注构建过程的细节，对于新手开发者也十分的友好。&lt;/p&gt;
&lt;p&gt;浏览器打开 &lt;code&gt;http://127.0.0.1:8082/simple/&lt;/code&gt;，也可以看到上传的包。下载是不需要认证的。&lt;/p&gt;
&lt;h3 id="universal-add_2"&gt;新项目中安装 universal-add&lt;/h3&gt;
&lt;p&gt;打开新的命令行窗口，创建一个测试用的新项目 testproj&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;testproj&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;testproj&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;universal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8082&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;
&lt;span class="n"&gt;Resolved&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="n"&gt;testproj&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.1.0&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;universal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.1.0&lt;/span&gt;
    &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="n"&gt;v8&lt;/span&gt;&lt;span class="mf"&gt;.3.1&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;from universal_add import add; print(add(&amp;quot;hello &amp;quot;, &amp;quot;42&amp;quot;))&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;安装 universal-add 依赖的时候，用&lt;code&gt;--index&lt;/code&gt;指定本地搭建的 pypiserver 服务。通过 &lt;code&gt;uv tree&lt;/code&gt; 和 &lt;code&gt;uv run ...&lt;/code&gt; 验证确认功能正常可用。 &lt;/p&gt;
&lt;h2 id="_3"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文基于 uv 完整演示了 Python 包从创建、开发、构建、发布到私有 PyPI 的流程，同时说明了 PEP 517/518 下前端与后端解耦的构建体系。借助 uv 的工具链和 pypiserver 的轻量部署，可以在本地快速完成包开发与分发的闭环验证。&lt;/p&gt;
&lt;p&gt;在新标准中，Python 包的分发已经变得十分简单和新手友好，降低了构建门槛。对 Python 生态发展具有里程碑的意义。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/python-uv-build-publish.html</guid>
      <pubDate>Sun, 15 Mar 2026 23:33:21 +0800</pubDate>
    </item>
    <item>
      <title>极快的python项目管理工具：uv使用指南</title>
      <link>http://xnow.me/programs/python-uv-tutorial.html</link>
      <description>&lt;p&gt;&lt;img alt="trees-by-the-lake" src="/usr/uploads/2026/03/1772979180.9971545.png" /&gt;&lt;/p&gt;
&lt;p&gt;上一篇文章介绍了新一代的 Python 项目管理工具 Poetry，解决了 Python 项目难以管理的问题。本文将介绍另一款 Python 项目管理工具 &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt;，uv 是由 Astral 团队打造的下一代 Python 项目与包管理工具，目标是替代 pip、pip-tools 和 virtualenv ，为 Python 生态带来更快的依赖解析和安装体验。&lt;/p&gt;
&lt;p&gt;uv 官网的介绍是 &lt;strong&gt;用 Rust 编写的极快速的 Python 打包和项目管理工具&lt;/strong&gt;。uv 之所以快，不仅因为用 Rust 重构，还在于其全局缓存与依赖解析算法优化。uv 仍在快速迭代中，强烈推荐 Python 开发者使用。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="trees-by-the-lake" src="/usr/uploads/2026/03/1772979180.9971545.png" /&gt;&lt;/p&gt;
&lt;p&gt;上一篇文章介绍了新一代的 Python 项目管理工具 Poetry，解决了 Python 项目难以管理的问题。本文将介绍另一款 Python 项目管理工具 &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt;，uv 是由 Astral 团队打造的下一代 Python 项目与包管理工具，目标是替代 pip、pip-tools 和 virtualenv ，为 Python 生态带来更快的依赖解析和安装体验。&lt;/p&gt;
&lt;p&gt;uv 官网的介绍是 &lt;strong&gt;用 Rust 编写的极快速的 Python 打包和项目管理工具&lt;/strong&gt;。uv 之所以快，不仅因为用 Rust 重构，还在于其全局缓存与依赖解析算法优化。uv 仍在快速迭代中，强烈推荐 Python 开发者使用。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2 id="_1"&gt;版本信息&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;OS: Debian 13 (Trixie)&lt;/li&gt;
&lt;li&gt;Python: 3.13&lt;/li&gt;
&lt;li&gt;uv: 0.10.7&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="uv"&gt;安装 uv&lt;/h2&gt;
&lt;p&gt;建议用 pipx 安装 uv&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/pypi/simple
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;--version
uv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.10.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv help&lt;/code&gt; 可以列出 uv 支持的所有命令。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;
...
&lt;span class="w"&gt;  &lt;/span&gt;run&lt;span class="w"&gt;                        &lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;script
&lt;span class="w"&gt;  &lt;/span&gt;init&lt;span class="w"&gt;                       &lt;/span&gt;Create&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;project
&lt;span class="w"&gt;  &lt;/span&gt;add&lt;span class="w"&gt;                        &lt;/span&gt;Add&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;project
&lt;span class="w"&gt;  &lt;/span&gt;remove&lt;span class="w"&gt;                     &lt;/span&gt;Remove&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;project
&lt;span class="w"&gt;  &lt;/span&gt;version&lt;span class="w"&gt;                    &lt;/span&gt;Read&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="s1"&gt;&amp;#39;s version&lt;/span&gt;
&lt;span class="s1"&gt;  sync                       Update the project&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;environment
&lt;span class="w"&gt;  &lt;/span&gt;lock&lt;span class="w"&gt;                       &lt;/span&gt;Update&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;lockfile
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_2"&gt;项目管理&lt;/h2&gt;
&lt;p&gt;uv 既可以用于新项目，也可以快速适配到已有的 Python 项目中。其用法十分简单直观。&lt;/p&gt;
&lt;h3 id="_3"&gt;创建项目&lt;/h3&gt;
&lt;p&gt;无论是创建新项目，还是将已有项目交给 uv 管理，都使用 &lt;code&gt;uv init&lt;/code&gt; 命令。区别在于：创建新项目需要传递项目名（即目录名）；初始化已有项目则不需要任何参数。&lt;/p&gt;
&lt;p&gt;下面创建一个新项目，看看 uv 做了哪些事。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;newproj
Initialized&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;newproj&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/tmp/newproj&lt;span class="sb"&gt;`&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;newproj
.&lt;span class="w"&gt;  &lt;/span&gt;..&lt;span class="w"&gt;  &lt;/span&gt;.git&lt;span class="w"&gt;  &lt;/span&gt;.gitignore&lt;span class="w"&gt;  &lt;/span&gt;main.py&lt;span class="w"&gt;  &lt;/span&gt;pyproject.toml&lt;span class="w"&gt;  &lt;/span&gt;.python-version&lt;span class="w"&gt;  &lt;/span&gt;README.md

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;newproj
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;main.py
Using&lt;span class="w"&gt; &lt;/span&gt;CPython&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.5&lt;span class="w"&gt; &lt;/span&gt;interpreter&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/python3.13&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 使用系统中的Python版本&lt;/span&gt;
Creating&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;.venv&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 创建虚拟环境&lt;/span&gt;
Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;newproj!&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="c1"&gt;# 虚拟环境中运行脚本&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 虚拟环境中的Python&lt;/span&gt;
/tmp/newproj/.venv/bin/python&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 实际是 /usr/bin/python3.13 的软链接&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;uv init&lt;/code&gt; 创建名为 newproj 的新项目。可以用 &lt;code&gt;--python 3.xx&lt;/code&gt; 指定虚拟环境 Python 版本；不指定时使用系统全局版本。&lt;/li&gt;
&lt;li&gt;查看新项目目录内容：&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.git&lt;/code&gt; 和 &lt;code&gt;.gitignore&lt;/code&gt; 是基础的 Git 目录和文件；&lt;code&gt;.gitignore&lt;/code&gt; 内含常见忽略项，例如 &lt;code&gt;__pycache__/&lt;/code&gt;、&lt;code&gt;.venv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main.py&lt;/code&gt; 是示例脚本，包含基础 &lt;code&gt;main&lt;/code&gt; 函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 是 PEP 定义的标准项目配置文件，uv 大多数操作都围绕它展开。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.python-version&lt;/code&gt; 记录项目使用的 Python 版本，例如 3.13。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;README.md&lt;/code&gt; 是项目说明文档，默认为空。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;uv run &amp;lt;command&amp;gt;&lt;/code&gt; 会在当前 uv 环境中运行命令，并自动创建 &lt;code&gt;.venv&lt;/code&gt; 目录和 &lt;code&gt;uv.lock&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 内容很精简，仅包含基本项目信息，建议按实际需要调整。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;newproj&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Add your description here&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;readme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;README.md&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;requires-python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;gt;=3.13&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv init&lt;/code&gt; 可通过参数决定项目类型，不同类型的目录结构略有区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--bare&lt;/code&gt;：仅创建 &lt;code&gt;pyproject.toml&lt;/code&gt;，不创建其他文件和目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--package&lt;/code&gt;：创建 &lt;code&gt;src&lt;/code&gt; 布局目录（如 &lt;code&gt;src/包名&lt;/code&gt;）。&lt;code&gt;pyproject.toml&lt;/code&gt; 额外包含 &lt;code&gt;[project.scripts]&lt;/code&gt; 和 &lt;code&gt;[build-system]&lt;/code&gt;，用于运行和打包。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--lib&lt;/code&gt;：与 &lt;code&gt;--package&lt;/code&gt; 类似，但不包含 &lt;code&gt;[project.scripts]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--app&lt;/code&gt;：应用类型项目，默认结构。采用扁平布局，无 &lt;code&gt;src&lt;/code&gt; 目录，&lt;code&gt;pyproject.toml&lt;/code&gt; 中也没有运行或打包配置段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--script&lt;/code&gt;：不创建 &lt;code&gt;pyproject.toml&lt;/code&gt;，仅创建一个脚本文件，文件开头包含注释形式的 &lt;code&gt;pyproject.toml&lt;/code&gt; 片段，用于管理脚本依赖。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些选项仅决定初始化时的目录结构，项目用途与使用方式仍可按需调整。&lt;/p&gt;
&lt;h3 id="pypi"&gt;PyPI 源&lt;/h3&gt;
&lt;p&gt;国内从官方 PyPI 源安装依赖通常较慢，可设定国内镜像作为默认源以提升速度。在 &lt;code&gt;pyproject.toml&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[[tool.uv.index]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aliyun&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://mirrors.aliyun.com/pypi/simple&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_4"&gt;安装和卸载&lt;/h3&gt;
&lt;p&gt;uv 使用 &lt;code&gt;add&lt;/code&gt; 和 &lt;code&gt;remove&lt;/code&gt; 管理依赖的安装与卸载。&lt;/p&gt;
&lt;p&gt;进入 newproj 目录，安装依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt; &lt;/span&gt;httpx

$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-A3&lt;span class="w"&gt; &lt;/span&gt;^dependencies&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aiohttp&amp;gt;=3.13.3&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;httpx&amp;gt;=0.28.1&amp;quot;&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;安装的依赖会记录在 &lt;code&gt;pyproject.toml&lt;/code&gt; 中。版本区间写法如 &lt;code&gt;&amp;gt;=3.13,&amp;lt;3.15&lt;/code&gt;，该写法可用于 Python 版本约束与依赖版本约束。&lt;/p&gt;
&lt;p&gt;卸载依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;aiohttp
$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-A3&lt;span class="w"&gt; &lt;/span&gt;^dependencies&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;httpx&amp;gt;=0.28.1&amp;quot;&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;卸载后，只剩一项依赖。 &lt;/p&gt;
&lt;p&gt;在添加或删除依赖时，uv 会完成 3件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将依赖下载到 uv 的用户级全局缓存中，并从缓存安装到虚拟环境。&lt;/li&gt;
&lt;li&gt;将依赖写入 &lt;code&gt;pyproject.toml&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;将依赖解析结果写入 &lt;code&gt;uv.lock&lt;/code&gt;。&lt;code&gt;uv.lock&lt;/code&gt; 是项目依赖快照，包含直接与间接依赖、来源、版本与哈希等信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;uv 还提供几种常见的依赖安装方式：&lt;/p&gt;
&lt;p&gt;从 &lt;code&gt;requirements.txt&lt;/code&gt; 文件中安装依赖，并写入 &lt;code&gt;pyproject.toml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;也可以将 &lt;code&gt;uv.lock&lt;/code&gt; 中的依赖导出为 &lt;code&gt;requirements.txt&lt;/code&gt; 格式：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;升级特定依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--upgrade&lt;span class="w"&gt; &lt;/span&gt;requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;将项目上传到 Git 仓库时，不应提交 &lt;code&gt;.venv&lt;/code&gt; 目录，一般只需要提交 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;uv.lock&lt;/code&gt;。运行时基于 &lt;code&gt;uv.lock&lt;/code&gt;，&lt;code&gt;uv sync&lt;/code&gt; 能快速恢复项目依赖，精确到每个依赖的具体版本。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看项目的依赖关系树形图&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tree
newproj&lt;span class="w"&gt; &lt;/span&gt;v0.1.0
└──&lt;span class="w"&gt; &lt;/span&gt;httpx&lt;span class="w"&gt; &lt;/span&gt;v0.28.1
&lt;span class="w"&gt;    &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;anyio&lt;span class="w"&gt; &lt;/span&gt;v4.12.1
&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;idna&lt;span class="w"&gt; &lt;/span&gt;v3.11
&lt;span class="w"&gt;    &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;certifi&lt;span class="w"&gt; &lt;/span&gt;v2026.2.25
&lt;span class="w"&gt;    &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;httpcore&lt;span class="w"&gt; &lt;/span&gt;v1.0.9
&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;certifi&lt;span class="w"&gt; &lt;/span&gt;v2026.2.25
&lt;span class="w"&gt;    &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;h11&lt;span class="w"&gt; &lt;/span&gt;v0.16.0
&lt;span class="w"&gt;    &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;idna&lt;span class="w"&gt; &lt;/span&gt;v3.11
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_5"&gt;依赖组&lt;/h3&gt;
&lt;p&gt;添加两个依赖组，分别为 dev 和 test，用于管理开发与测试阶段依赖。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;ruff&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;dev
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;pytest&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;uv add &lt;PACKAGES&gt; --group dev 等价于 &lt;code&gt;uv add --dev &amp;lt;PACKAGES&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;检查 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件内容，依赖组定义在 &lt;code&gt;dependency-groups&lt;/code&gt; 中：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-A6&lt;span class="w"&gt; &lt;/span&gt;dependency-groups&lt;span class="w"&gt;  &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;dependency-groups&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ruff&amp;gt;=0.15.5&amp;quot;&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytest&amp;gt;=9.0.2&amp;quot;&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv sync&lt;/code&gt; 提供以下选项以决定安装哪些依赖组：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--no-dev&lt;/code&gt; ： 禁用dev依赖组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--only-dev&lt;/code&gt;：仅包含dev依赖组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--group &amp;lt;GROUP&amp;gt;&lt;/code&gt; ：指定特定依赖组，&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-group &amp;lt;NO_GROUP&amp;gt;&lt;/code&gt;：排除指定依赖组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-default-groups&lt;/code&gt; ：排除默认的依赖组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--only-group &amp;lt;ONLY_GROUP&amp;gt;&lt;/code&gt;：仅包含特定的依赖组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--all-groups&lt;/code&gt; ：安装所有依赖组。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了依赖组 &lt;code&gt;dependency-groups&lt;/code&gt; 之外，uv 还支持可选依赖（optional dependencies）。两者用法相近，但职责不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dependency-groups&lt;/code&gt; 用 &lt;code&gt;--group&lt;/code&gt; 之类的参数控制；&lt;code&gt;optional dependencies&lt;/code&gt; 用 &lt;code&gt;--extra &amp;lt;EXTRA&amp;gt;&lt;/code&gt; 控制。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependency-groups&lt;/code&gt; 记录在 &lt;code&gt;pyproject.toml&lt;/code&gt; 的 &lt;code&gt;[dependency-groups]&lt;/code&gt; 表中；&lt;code&gt;optional dependencies&lt;/code&gt; 记录在 &lt;code&gt;[project.optional-dependencies]&lt;/code&gt; 中。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependency-groups&lt;/code&gt; 一般用于开发、测试、CI 等阶段依赖；&lt;code&gt;optional dependencies&lt;/code&gt; 更适合库项目的可选功能（如不同数据库后端）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;uv 各子命令对 &lt;code&gt;dependency-groups&lt;/code&gt; 与 &lt;code&gt;optional dependencies&lt;/code&gt; 的操作支持相对一致，可按项目需求选择。&lt;/p&gt;
&lt;h3 id="uvlock"&gt;uv.lock&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv.lock&lt;/code&gt; 记录当前解析出的依赖集合，是项目依赖的快照。使用 &lt;code&gt;uv sync&lt;/code&gt; 可恢复出一致的项目环境，对应用类型的 Python 项目尤其有用。&lt;/p&gt;
&lt;p&gt;如果 &lt;code&gt;uv.lock&lt;/code&gt; 丢失，可用 &lt;code&gt;uv lock&lt;/code&gt; 重新生成 &lt;code&gt;uv.lock&lt;/code&gt;。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;可以手动编辑 pyproject.toml， 但千万不要手动编辑 uv.lock 文件&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="link-mode"&gt;link-mode 与缓存&lt;/h3&gt;
&lt;p&gt;uv 可通过环境变量控制行为，其中 &lt;code&gt;UV_LINK_MODE&lt;/code&gt; 用于决定从全局缓存安装软件包的方式，可选值有 &lt;code&gt;clone&lt;/code&gt;、&lt;code&gt;copy&lt;/code&gt;、&lt;code&gt;hardlink&lt;/code&gt;、&lt;code&gt;symlink&lt;/code&gt;。从缓存获取依赖，是 uv 速度提升的重要原因之一。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;clone&lt;/code&gt;：将源目录中的包克隆（写入时复制）到目标目录，默认选项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;copy&lt;/code&gt;：将包从源位置复制到目标位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hardlink&lt;/code&gt;：将源包硬链接到目标位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;symlink&lt;/code&gt;：将源目录中的包以符号链接方式指向目标目录。不建议使用此模式，清理缓存可能导致依赖失效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了环境变量，uv 也支持使用 &lt;code&gt;--link-mode&lt;/code&gt; 指定模式，该行为也可写入 &lt;code&gt;pyproject.toml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="n"&gt;link-mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;copy&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv cache dir&lt;/code&gt; 查看缓存目录位置：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;dir
~/.cache/uv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv cache size&lt;/code&gt; 查看缓存大小：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;--human
&lt;span class="m"&gt;26&lt;/span&gt;.7MiB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv cache clean&lt;/code&gt; 清理缓存。可指定删除特定包，默认删除全部缓存。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;clean&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# 清理系统的全部缓存&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;size&lt;span class="w"&gt; &lt;/span&gt;--human&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 查看清理后，缓存大小为0&lt;/span&gt;
&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uv cache prune&lt;/code&gt; 命令比&lt;code&gt;uv cache clean&lt;/code&gt; 智能一些，只会清理不再使用的缓存包。&lt;/p&gt;
&lt;p&gt;除 &lt;code&gt;--link-mode&lt;/code&gt; 外，uv 还支持 &lt;code&gt;--offline&lt;/code&gt; 模式禁用网络，仅使用本地缓存安装依赖；若依赖缺失则直接报错。该行为也可在 &lt;code&gt;pyproject.toml&lt;/code&gt; 中配置：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="n"&gt;offline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_6"&gt;虚拟环境&lt;/h2&gt;
&lt;p&gt;uv 不仅可以使用系统中已有的 Python，还能从 &lt;a href="https://github.com/astral-sh/python-build-standalone"&gt;uv 的 Github 仓库&lt;/a&gt;中在线下载和安装指定版本的 Python。&lt;/p&gt;
&lt;h3 id="python"&gt;Python 版本管理&lt;/h3&gt;
&lt;p&gt;uv 支持在线下载并安装指定的 Python 版本。查看 uv 安装 Python 的路径：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;dir
~/.local/share/uv/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看当前已安装和可安装的版本列表：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;
cpython-3.15.0a6-linux-x86_64-gnu&lt;span class="w"&gt;                 &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.15.0a6+freethreaded-linux-x86_64-gnu&lt;span class="w"&gt;    &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.14.3-linux-x86_64-gnu&lt;span class="w"&gt;                   &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.14.3+freethreaded-linux-x86_64-gnu&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.13.12-linux-x86_64-gnu&lt;span class="w"&gt;                  &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.13.12+freethreaded-linux-x86_64-gnu&lt;span class="w"&gt;     &lt;/span&gt;&amp;lt;download&lt;span class="w"&gt; &lt;/span&gt;available&amp;gt;
cpython-3.13.5-linux-x86_64-gnu&lt;span class="w"&gt;                   &lt;/span&gt;/usr/bin/python3.13&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 系统的全局 Python 版本&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在线安装 Python&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在项目中临时使用 Python 3.14 运行：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="mf"&gt;3.14&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;import sys; print(sys.version)&amp;#39;&lt;/span&gt;                                                                                  
&lt;span class="mf"&gt;3.14.3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Clang&lt;/span&gt; &lt;span class="mf"&gt;21.1.4&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;修改当前项目运行的 Python 版本：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;pin&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14
Pinned&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;.python-version&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14&lt;span class="sb"&gt;`&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;--version
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code&gt;uv python pin 3.14&lt;/code&gt; 会更新项目目录下的 &lt;code&gt;.python-version&lt;/code&gt; 为 3.14。由于仍满足 &lt;code&gt;pyproject.toml&lt;/code&gt; 中 &lt;code&gt;requires-python = "&amp;gt;=3.13"&lt;/code&gt; 的要求，所以无需修改 &lt;code&gt;pyproject.toml&lt;/code&gt;。若不满足约束，会报兼容性错误，需要手动修改 &lt;code&gt;pyproject.toml&lt;/code&gt; 的限制条件。&lt;/p&gt;
&lt;h3 id="uv-pip"&gt;uv pip&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;uv pip ...&lt;/code&gt; 在虚拟环境中安装包：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;ipython
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;ipython
ipython&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.11.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意：使用 &lt;code&gt;uv pip install&lt;/code&gt; 安装的软件包不会记录到 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;uv.lock&lt;/code&gt; 中，执行 &lt;code&gt;uv sync&lt;/code&gt; 时会被移除。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="uv_1"&gt;uv 的其他用法&lt;/h2&gt;
&lt;p&gt;除了上面的基础项目管理和虚拟环境等基础功能外，uv 还有一些其他很有用的玩法。&lt;/p&gt;
&lt;h3 id="uv-format"&gt;uv format&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv format&lt;/code&gt; 命令可以用于代码的格式化，底层使用 ruff ，支持如下两个参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--check&lt;/code&gt;：检查代码是否已经符合格式要求，不执行格式化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--diff&lt;/code&gt;：以 diff 的形式显示 format 将会对代码做哪些变动。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不加参数的情况下，&lt;code&gt;uv format&lt;/code&gt; 会默认自动对代码做格式化。也可以仅对指定 Python 文件做 format 格式化。&lt;/p&gt;
&lt;p&gt;uv 会自动寻找项目目录下和全局的 ruff 配置文件（比如：~/.config/ruff/ruff.toml），以便确定 ruff 的 format 规则。&lt;/p&gt;
&lt;h3 id="uvx-uv-tool"&gt;uvx 和 uv tool&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv tool&lt;/code&gt; 是 uv 提供的全局工具箱管理，用于安装或在隔离环境中运行 Python CLI 工具，功能类似 pipx。子命令包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;install/uninstall/list/upgrade&lt;/code&gt;：安装/卸载/列表/升级工具&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt;：运行工具，等价于 &lt;code&gt;uvx&lt;/code&gt;。工具不存在时会自动安装到缓存并运行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dir&lt;/code&gt;：查看工具安装路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;uv tool run&lt;/code&gt;（即 &lt;code&gt;uvx&lt;/code&gt;）的目标是“无需安装、即时运行”。它会创建临时环境用于运行，环境会被缓存以便复用，非常适合偶尔使用的工具。
&lt;code&gt;uv tool install&lt;/code&gt; 则将工具安装为全局可用命令。&lt;/p&gt;
&lt;p&gt;查看工具安装的目录&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;dir
~/.local/share/uv/tools
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code&gt;uvx&lt;/code&gt; 和 &lt;code&gt;uv tool run&lt;/code&gt; 临时安装并运行 httpx 工具：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;httpx&lt;span class="o"&gt;[&lt;/span&gt;cli&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://xnow.me&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# uv tool run 临时安装和测试&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt;  &lt;/span&gt;httpx&lt;span class="o"&gt;[&lt;/span&gt;cli&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://xnow.me&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;# uvx 临时安装和测试&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="c1"&gt;# 工具列表为空&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;安装 httpx 工具并检查安装路径：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;--force  # 安装 httpx 工具&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;检查工具列表&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;其中包含了&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.28.1&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;已经有了全局的&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;命令&lt;/span&gt;
&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;检查全局&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;命令&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;来自于&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;目录下&lt;/span&gt;
&lt;span class="n"&gt;lrwxrwxrwx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mar&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;因此，如果要经常使用的命令，建议用 &lt;code&gt;uv tool install&lt;/code&gt; 做安装和使用。&lt;/p&gt;
&lt;h3 id="_7"&gt;为特定依赖使用特定源&lt;/h3&gt;
&lt;p&gt;PyTorch 官方源有些奇怪，默认的 PyPI 中只有 GPU 版本的依赖库，如果要安装 CPU 版本的PyPI，则需要指定专门支持 CPU 的 PyPI 源。因此，可以在 &lt;code&gt;ptproject.toml&lt;/code&gt; 中添加专门的源，并指定 torch 包使用这个源安装。配置如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.uv.sources]&lt;/span&gt;
&lt;span class="na"&gt;torch = { index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pytorch-cpu&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;[[tool.uv.index]]&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pytorch-cpu&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;explicit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://download.pytorch.org/whl/cpu&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;某些场景下建议显式使用 PyTorch 官方 index，效果等价于 &lt;code&gt;-i https://download.pytorch.org/whl/cpu&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;explicit = true&lt;/code&gt; 表示默认不使用该源，只有明确指定时才会使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="run"&gt;run 的临时依赖&lt;/h3&gt;
&lt;p&gt;如果不想将临时依赖写入 dev 依赖组，可用 &lt;code&gt;--with&lt;/code&gt; 临时指定，例如为 Flask 的 shell 使用 IPython：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hello.py&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--with&lt;span class="w"&gt; &lt;/span&gt;flask-shell-ipython&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;shell
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="uv_2"&gt;uv 脚本&lt;/h3&gt;
&lt;p&gt;uv 的脚本管理功能在运维脚本场景中非常实用。使用 uv 初始化脚本时，脚本自动内置依赖管理，不创建 &lt;code&gt;pyproject.toml&lt;/code&gt;。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--script&lt;span class="w"&gt; &lt;/span&gt;test.py&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# 初始化脚本&lt;/span&gt;
Initialized&lt;span class="w"&gt; &lt;/span&gt;script&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;test.py&lt;span class="sb"&gt;`&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;test.py&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="c1"&gt;# 查看脚本内容&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# requires-python = &amp;quot;&amp;gt;=3.13&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = []&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

def&lt;span class="w"&gt; &lt;/span&gt;main&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;None:
&lt;span class="w"&gt;    &lt;/span&gt;print&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from test.py!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;__name__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;main&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;test.py&lt;/code&gt; 如果不存在， uv 会自动创建，内容如上。如果 test.py 脚本已经存在， uv 会在脚本已有内容的基础上，在文件开头增加依赖管理相关的注释行。&lt;/p&gt;
&lt;p&gt;可以手动或使用 uv 命令为该文件管理依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;requests&lt;span class="w"&gt; &lt;/span&gt;--script&lt;span class="w"&gt; &lt;/span&gt;test.py

$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;test.py&lt;span class="w"&gt;  &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = [&lt;/span&gt;
&lt;span class="c1"&gt;#     &amp;quot;requests&amp;quot;,&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;test.py
Installed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;23ms
Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;test.py!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uv add requests --script test.py&lt;/code&gt; 会将依赖更新到脚本文件开头。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run test.py&lt;/code&gt; 会解析脚本中定义的依赖并创建虚拟环境。脚本虚拟环境位于 &lt;code&gt;~/.cache/uv/environments-v2/&lt;/code&gt;，不同脚本之间彼此隔离。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果觉得频繁使用 &lt;code&gt;uv run&lt;/code&gt; 不便，可在脚本第一行添加 shebang：&lt;code&gt;#!/usr/bin/env -S uv run&lt;/code&gt;。授予可执行权限后，&lt;code&gt;./a.py&lt;/code&gt; 等价于 &lt;code&gt;uv run a.py&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;uv lock --script test.py&lt;/code&gt; 会创建或更新 &lt;code&gt;test.py.lock&lt;/code&gt;，记录脚本依赖及版本。&lt;/p&gt;
&lt;h3 id="uv_3"&gt;探索 uv 的其他用法&lt;/h3&gt;
&lt;p&gt;uv 项目仍在快速发展中，有些使用方法还在预览阶段。如果遇到问题，建议到 uv 的官方文档中寻求解答。uv 的配置项和环境变量尤其有用，建议看看。&lt;/p&gt;
&lt;p&gt;uv 配置项：https://docs.astral.sh/uv/reference/settings/
uv 环境变量：https://docs.astral.sh/uv/reference/environment/&lt;/p&gt;
&lt;p&gt;uv 的优势在于“快”和“可重复”。无论是日常开发还是脚本工具化，&lt;code&gt;uv init/add/sync&lt;/code&gt; 覆盖了大多数场景，配合 &lt;code&gt;uv.lock&lt;/code&gt; 能稳定复现依赖环境。&lt;/p&gt;
&lt;h2 id="_8"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文从日常开发需求入手，介绍了 uv 在 Python 项目开发各个阶段的用法。不论是初级还是资深Python研发工程师，都建议尝试uv，随着 uv 的发展，应该会有更多惊艳功能出现。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/python-uv-tutorial.html</guid>
      <pubDate>Sun, 08 Mar 2026 22:17:41 +0800</pubDate>
    </item>
    <item>
      <title>Python项目管理工具：Poetry 使用指南</title>
      <link>http://xnow.me/programs/python-poetry.html</link>
      <description>&lt;p&gt;&lt;img alt="sky-and-plane" src="/usr/uploads/2026/03/1772375058.9541836.png" /&gt;
在项目开发中，良好的代码管理工具至关重要。对于 Python 开发而言，核心诉求是管理项目依赖、虚拟环境、项目元信息等。近年来，一系列 PEP（Python 增强提案）规范了使用 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件进行项目依赖、构建和元信息管理。伴随着标准落地，许多新工具渐次发布，首当其冲的就是 &lt;a href="https://python-poetry.org/"&gt;Poetry&lt;/a&gt;。Poetry 是用于 Python 的依赖管理和打包工具，设计目标是简化和提升 Python 包的创建、管理与发布过程。在 &lt;a href="https://python-poetry.org/"&gt;Poetry 官网&lt;/a&gt; 中对其的介绍是：&lt;strong&gt;Poetry 是让 Python 打包和依赖管理更加轻松的工具。&lt;/strong&gt;&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="sky-and-plane" src="/usr/uploads/2026/03/1772375058.9541836.png" /&gt;
在项目开发中，良好的代码管理工具至关重要。对于 Python 开发而言，核心诉求是管理项目依赖、虚拟环境、项目元信息等。近年来，一系列 PEP（Python 增强提案）规范了使用 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件进行项目依赖、构建和元信息管理。伴随着标准落地，许多新工具渐次发布，首当其冲的就是 &lt;a href="https://python-poetry.org/"&gt;Poetry&lt;/a&gt;。Poetry 是用于 Python 的依赖管理和打包工具，设计目标是简化和提升 Python 包的创建、管理与发布过程。在 &lt;a href="https://python-poetry.org/"&gt;Poetry 官网&lt;/a&gt; 中对其的介绍是：&lt;strong&gt;Poetry 是让 Python 打包和依赖管理更加轻松的工具。&lt;/strong&gt;&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;本文将通过实际操作的方式，一步步了解 Poetry 的功能与常见工作流。本文基于以下版本进行演示：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;OS：Debian 13（Trixie）
Python：3.13
poetry：2.3.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Poetry 还在快速更新迭代中，有些配置和用法可能会因版本差异而不同，请注意区分。&lt;/p&gt;
&lt;h2 id="_1"&gt;安装和使用&lt;/h2&gt;
&lt;p&gt;安装 Poetry 的方法见官方文档： &lt;a href="https://python-poetry.org/docs/#installation"&gt;https://python-poetry.org/docs&lt;/a&gt;。不同平台有各自的安装方法，建议使用 pipx 安装。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;poetry
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;--version
Poetry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.3.2&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Poetry 版本升级方法：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;poetry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Poetry 常用命令速览：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;new
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;init
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;remove
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;sync
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_2"&gt;项目管理&lt;/h2&gt;
&lt;p&gt;以下命令可以用于了解 Poetry 的用法和版本信息等。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;list
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;about
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;子命令介绍：
+ &lt;code&gt;list&lt;/code&gt;  查看所有 Poetry 支持的子命令。
+ &lt;code&gt;about&lt;/code&gt; 查看 Poetry 的版本等信息。&lt;/p&gt;
&lt;p&gt;Poetry 有一个全局性的 &lt;code&gt;--verbose&lt;/code&gt; 选项，或者 &lt;code&gt;-v/-vv/-vvv&lt;/code&gt;，可以用来观察 Poetry 的 debug 信息和具体的运行过程。&lt;/p&gt;
&lt;h3 id="poetry"&gt;Poetry 的全局配置&lt;/h3&gt;
&lt;p&gt;查看 Poetry 的默认配置：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--list
cache-dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.cache/pypoetry&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# 缓存目录&lt;/span&gt;
data-dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.local/share/pypoetry&amp;quot;&lt;/span&gt;
installer.max-workers&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;null
virtualenvs.path&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{cache-dir}/virtualenvs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 项目虚拟环境目录&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;设置 Poetry 的配置项。修改 Poetry 的全局配置会影响 Poetry 的行为，谨慎修改。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;virtualenvs.in-project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;repositories.aliyun&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/pypi/simple/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;修改 &lt;code&gt;virtualenvs.in-project&lt;/code&gt; 配置项为 &lt;code&gt;true&lt;/code&gt;。默认情况下，Poetry 会在 &lt;code&gt;virtualenvs.path&lt;/code&gt; 配置规定的目录下创建项目的虚拟环境，修改该项为 &lt;code&gt;true&lt;/code&gt; 后，Poetry 会在项目目录下创建虚拟环境，目录为 &lt;code&gt;.venv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;配置一个名为 aliyun 的 PyPI 源，修改为国内源以提升依赖安装速度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Poetry 的配置保存在 &lt;code&gt;~/.config/pypoetry/config.toml&lt;/code&gt; 文件中。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;~/.config/pypoetry/config.toml
&lt;span class="o"&gt;[&lt;/span&gt;virtualenvs&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;-project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;repositories.aliyun&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://mirrors.aliyun.com/pypi/simple/&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上是比较常见的 Poetry 全局指令。接下来是和项目管理相关的内容。&lt;/p&gt;
&lt;h3 id="_3"&gt;创建新项目&lt;/h3&gt;
&lt;p&gt;使用 Poetry 的 &lt;code&gt;new&lt;/code&gt; 子指令创建一个全新的项目。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;myproj
Created&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;myproj&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;myproj
$&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;myproj
myproj
├──&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml
├──&lt;span class="w"&gt; &lt;/span&gt;README.md
├──&lt;span class="w"&gt; &lt;/span&gt;src
│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;myproj
│&lt;span class="w"&gt;       &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
└──&lt;span class="w"&gt; &lt;/span&gt;tests
&lt;span class="w"&gt;    &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;new&lt;/code&gt; 子命令创建新的 Python 项目。新项目目录包含基础的项目结构，除 &lt;code&gt;pyproject.toml&lt;/code&gt; 外还会生成 &lt;code&gt;README.md&lt;/code&gt;、&lt;code&gt;tests&lt;/code&gt; 等基础文件，内容多为占位信息。&lt;/p&gt;
&lt;p&gt;Python 项目有两种常见结构：一种是把包放在项目的顶级目录下，一般称为 flat 布局，许多 Web 项目都是这么做的；另一种是把包放到项目的 src 目录下，称为 src 布局，许多库项目采取了这种方式。Poetry 默认采用 src 布局，如果想使用 flat 布局，可以传入 &lt;code&gt;--flat&lt;/code&gt; 参数。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;myproj2&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;--flat
Created&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;myproj2

$&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;myproj2
myproj2
├──&lt;span class="w"&gt; &lt;/span&gt;app
│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
├──&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml
├──&lt;span class="w"&gt; &lt;/span&gt;README.md
└──&lt;span class="w"&gt; &lt;/span&gt;tests
&lt;span class="w"&gt;    &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--name&lt;/code&gt; 参数用于指定创建项目的包名，如果不指定，包名和项目名一样。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--flat&lt;/code&gt; 指定创建 flat 布局项目，包名 app 就在项目顶级目录下，没有 src 目录了。生成的 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件也有细微区别。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="_4"&gt;初始化已有项目&lt;/h3&gt;
&lt;p&gt;如果现有项目要切换到 Poetry 进行管理，可以使用 &lt;code&gt;init&lt;/code&gt; 指令，会在现有项目中创建 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;myproj3
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;myproj3
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;--no-interaction&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 不使用交互式输入项目信息&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;ls
pyproject.toml&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 仅仅创建了 pyproject.toml 文件&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Poetry 初始化项目的工作十分简单，只新增了一个 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件。如果 &lt;code&gt;init&lt;/code&gt; 指令不使用 &lt;code&gt;--no-interaction&lt;/code&gt; 参数，Poetry 会出现交互式配置提示，需要一步步填入项目元信息。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;check&lt;/code&gt; 子命令用于检查 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件的格式是否正确。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;
All&lt;span class="w"&gt; &lt;/span&gt;set!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_5"&gt;项目配置&lt;/h3&gt;
&lt;p&gt;Poetry 通过 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件实现大部分管理功能，部分 Poetry 子命令（比如 &lt;code&gt;add&lt;/code&gt;、&lt;code&gt;remove&lt;/code&gt; 等）会修改这个文件，这个文件也可以手动修改。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 并不是 Poetry 的发明，&lt;a href="https://peps.python.org/pep-0518/"&gt;PEP 518&lt;/a&gt; 定义了该文件作为构建配置入口，&lt;a href="https://peps.python.org/pep-0621/"&gt;PEP 621&lt;/a&gt; 定义了 &lt;code&gt;[project]&lt;/code&gt; 元信息规范。其他符合 PEP 规范的工具也可以使用这个文件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下是 myproj2 项目的 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;myproj2

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;project&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;authors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Your Name&amp;quot;&lt;/span&gt;,email&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;you@example.com&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;readme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;README.md&amp;quot;&lt;/span&gt;
requires-python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;gt;=3.13&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;build-system&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;poetry-core&amp;gt;=2.0.0,&amp;lt;3.0.0&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
build-backend&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;poetry.core.masonry.api&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在 TOML 格式中，方括号代表 table，类似一个子 JSON，其中的等号定义内容相当于 JSON 中的键值对。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[project]&lt;/code&gt; 定义了项目的元信息，例如 name、version、description、authors 等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requires-python&lt;/code&gt; 指定本项目依赖的 Python 版本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt; 用于指定本项目的依赖。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[build-system]&lt;/code&gt; 中指定了库项目构建相关的依赖和构建后端。使用 Poetry 打包/发布时必须保留该部分；即便是应用项目，保留默认配置也更稳妥。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pypi"&gt;PyPI 源管理&lt;/h3&gt;
&lt;p&gt;在 Poetry 管理的项目中，可以使用 &lt;code&gt;source&lt;/code&gt; 子命令管理 PyPI 源，&lt;code&gt;source&lt;/code&gt; 子命令下有 &lt;code&gt;add&lt;/code&gt;、&lt;code&gt;show&lt;/code&gt;、&lt;code&gt;remove&lt;/code&gt; 等子命令用于对 PyPI 源进行管理。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;aliyun&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/pypi/simple
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;douban&lt;span class="w"&gt; &lt;/span&gt;https://pypi.doubanio.com/simple

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;show
&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt;      &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;aliyun&lt;span class="w"&gt;                                 &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt;       &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/pypi/simple&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;priority&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;primary&lt;span class="w"&gt;                              &lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt;      &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;douban&lt;span class="w"&gt;                           &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt;       &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;https://pypi.doubanio.com/simple&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;priority&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;primary
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;aliyun 和 douban 两个源的优先级都是 &lt;code&gt;primary&lt;/code&gt;，当存在多个 &lt;code&gt;primary&lt;/code&gt; 源时，Poetry 会按它们在 &lt;code&gt;pyproject.toml&lt;/code&gt; 中的顺序进行查找；未显式指定 &lt;code&gt;--source&lt;/code&gt; 的依赖通常会优先走第一个 &lt;code&gt;primary&lt;/code&gt; 源。&lt;/p&gt;
&lt;p&gt;如果需要从特定的 source 安装依赖，可以用 &lt;code&gt;--source&lt;/code&gt; 指定源站。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;douban
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在运行上面的命令之后，目录下会产生 &lt;code&gt;poetry.lock&lt;/code&gt; 文件，它非常重要，记录了当前项目的依赖关系和依赖版本等信息，相当于一个快照，&lt;code&gt;poetry sync&lt;/code&gt; 等命令会读取 &lt;code&gt;poetry.lock&lt;/code&gt; 还原出完全一致的依赖环境。&lt;/p&gt;
&lt;p&gt;再次查看 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件，重点看看其中的依赖部分：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;requires-python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;gt;=3.13&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flask (&amp;gt;=3.1.3,&amp;lt;4.0.0)&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;[[tool.poetry.source]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aliyun&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://mirrors.aliyun.com/pypi/simple&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;primary&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;[[tool.poetry.source]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;douban&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://pypi.doubanio.com/simple&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;primary&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;douban&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;project.dependencies&lt;/code&gt; 中指明了项目依赖的 flask 版本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[tool.poetry.dependencies]&lt;/code&gt; 中指明了 Flask 依赖来自的源。这在某些场景很有用，比如英伟达的 CUDA 源分为 CPU 版本和 GPU 版本，CPU 就要使用特定的源才能安装。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="package"&gt;非 package 项目&lt;/h3&gt;
&lt;p&gt;Poetry 项目分两种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;默认是 &lt;code&gt;package&lt;/code&gt; 模式：意味项目需要被打包和发布，这种模式下 &lt;code&gt;pyproject.toml&lt;/code&gt; 中的 &lt;code&gt;version&lt;/code&gt; 是强制必填字段，运行 &lt;code&gt;poetry install&lt;/code&gt; 会安装当前项目。一般采用 src 项目布局。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;non-package&lt;/code&gt; 模式：意味这是应用类型的项目。一般使用 flat 项目布局。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;[tool.poetry]&lt;/code&gt; 中添加如下配置，指定为 non-package 模式。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;一般来说，用什么模式的区别不大，项目的布局更重要一些。不管是不是 package 模式，都建议设置 &lt;code&gt;version&lt;/code&gt; 字段。&lt;/p&gt;
&lt;h3 id="poetry-run"&gt;poetry run&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;poetry run&lt;/code&gt; 会在当前的 Poetry 环境中运行命令。如果虚拟环境还不存在，运行这个命令会创建虚拟环境。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;python
/tmp/myproj2/.venv/bin/python

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;--version
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.5
Flask&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.1.3
Werkzeug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.1.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_6"&gt;依赖管理&lt;/h2&gt;
&lt;h3 id="python"&gt;管理 Python 版本&lt;/h3&gt;
&lt;p&gt;较新版本的 Poetry 支持安装特定版本的 Python 了。先看看当前虚拟环境中的 Python 版本。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;info
Virtualenv
Python:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.5
Implementation:&lt;span class="w"&gt; &lt;/span&gt;CPython
Path:&lt;span class="w"&gt;           &lt;/span&gt;/tmp/myproj2/.venv
Executable:&lt;span class="w"&gt;     &lt;/span&gt;/tmp/myproj2/.venv/bin/python
Valid:&lt;span class="w"&gt;          &lt;/span&gt;True

Base
Platform:&lt;span class="w"&gt;   &lt;/span&gt;linux
OS:&lt;span class="w"&gt;         &lt;/span&gt;posix
Python:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.5
Path:&lt;span class="w"&gt;       &lt;/span&gt;/usr
Executable:&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/python3.13
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;和系统 Python 版本一致，是 3.13。Poetry 的 &lt;code&gt;python&lt;/code&gt; 子命令有 &lt;code&gt;install&lt;/code&gt;、&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;remove&lt;/code&gt; 3 个子命令管理项目的 Python 版本。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;--all
&lt;span class="w"&gt; &lt;/span&gt;Version&lt;span class="w"&gt;  &lt;/span&gt;Implementation&lt;span class="w"&gt; &lt;/span&gt;Manager&lt;span class="w"&gt; &lt;/span&gt;Path
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14.3t&lt;span class="w"&gt;  &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;Poetry&lt;span class="w"&gt;  &lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;download
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.14.3&lt;span class="w"&gt;   &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;Poetry&lt;span class="w"&gt;  &lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;download
...&lt;span class="w"&gt;    &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面的命令会列出所有可用的 Python 版本，&lt;code&gt;Available for download&lt;/code&gt; 表示可以下载安装。如果不使用 &lt;code&gt;--all&lt;/code&gt; 参数，只会列出本地已有的 Python 版本。&lt;/p&gt;
&lt;p&gt;安装 Python 3.11 试试看:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11
Downloading&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;installing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cpython&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;Done
Testing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cpython&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;Done

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;list
Version&lt;span class="w"&gt; &lt;/span&gt;Implementation&lt;span class="w"&gt; &lt;/span&gt;Manager&lt;span class="w"&gt; &lt;/span&gt;Path
&lt;span class="m"&gt;3&lt;/span&gt;.13.5&lt;span class="w"&gt;  &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;System&lt;span class="w"&gt;  &lt;/span&gt;/usr/bin/python3.13
&lt;span class="m"&gt;3&lt;/span&gt;.13.5&lt;span class="w"&gt;  &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;System&lt;span class="w"&gt;  &lt;/span&gt;/usr/bin/python3
&lt;span class="m"&gt;3&lt;/span&gt;.13.5&lt;span class="w"&gt;  &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;System&lt;span class="w"&gt;  &lt;/span&gt;/bin/python3.13
&lt;span class="m"&gt;3&lt;/span&gt;.13.5&lt;span class="w"&gt;  &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;System&lt;span class="w"&gt;  &lt;/span&gt;/bin/python3
&lt;span class="m"&gt;3&lt;/span&gt;.11.14&lt;span class="w"&gt; &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;Poetry&lt;span class="w"&gt;  &lt;/span&gt;~/.local/share/pypoetry/python/cpython@3.11.14/bin/python3.11
&lt;span class="m"&gt;3&lt;/span&gt;.11.14&lt;span class="w"&gt; &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;Poetry&lt;span class="w"&gt;  &lt;/span&gt;~/.local/share/pypoetry/python/cpython@3.11.14/bin/python3
&lt;span class="m"&gt;3&lt;/span&gt;.11.14&lt;span class="w"&gt; &lt;/span&gt;CPython&lt;span class="w"&gt;        &lt;/span&gt;Poetry&lt;span class="w"&gt;  &lt;/span&gt;~/.local/share/pypoetry/python/cpython@3.11.14/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;安装过程就是到 GitHub 的 &lt;a href="https://github.com/astral-sh/python-build-standalone/"&gt;python-build-standalone&lt;/a&gt; 仓库下载对应版本的 Python 包安装。&lt;/p&gt;
&lt;p&gt;可以看到 Poetry 把 Python 3.11 安装到 &lt;code&gt;~/.local/share/pypoetry/python/&lt;/code&gt; 目录下了。&lt;/p&gt;
&lt;p&gt;然后修改 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件，编辑 &lt;code&gt;requires-python&lt;/code&gt; 一行，指定使用 Python 3.11 版本：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;requires-python = &amp;quot;&amp;gt;=3.11,&amp;lt;3.12&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;删除旧的 3.13 版本虚拟环境，然后切换为 3.11 并重建虚拟环境。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;
&lt;span class="n"&gt;Deleted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtualenv&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myproj2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;

&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtualenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myproj2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;
&lt;span class="n"&gt;Writing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;

&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;

&lt;span class="n"&gt;Virtualenv&lt;/span&gt;
&lt;span class="nl"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="mf"&gt;3.11.14&lt;/span&gt;
&lt;span class="nl"&gt;Implementation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPython&lt;/span&gt;
&lt;span class="nl"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myproj2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;
&lt;span class="nl"&gt;Executable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myproj2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="nl"&gt;Valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt;
&lt;span class="nl"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;
&lt;span class="nl"&gt;OS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;posix&lt;/span&gt;
&lt;span class="nl"&gt;Python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;3.11.14&lt;/span&gt;
&lt;span class="nl"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pypoetry&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cpython&lt;/span&gt;&lt;span class="mf"&gt;@3.11.14&lt;/span&gt;
&lt;span class="nl"&gt;Executable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pypoetry&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cpython&lt;/span&gt;&lt;span class="mf"&gt;@3.11.14&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="mf"&gt;.11&lt;/span&gt;

&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.11.14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;先删除旧的虚拟环境，然后运行&lt;code&gt;poetry update&lt;/code&gt; 会新建 3.11 版本的虚拟环境。新的 &lt;code&gt;.venv&lt;/code&gt; 环境里的 Python 版本是 3.11，也显示了 3.11 环境基于 Poetry 安装在用户目录下的 Python。&lt;/p&gt;
&lt;h3 id="_7"&gt;管理依赖&lt;/h3&gt;
&lt;p&gt;从 PyPI 里查找包，可以看到把 aliyun 这个 PyPI 源中匹配 aiohttp 的包都列出来了。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;aiohttp
&lt;span class="w"&gt; &lt;/span&gt;Package&lt;span class="w"&gt;                                                    &lt;/span&gt;Version&lt;span class="w"&gt;     &lt;/span&gt;Source&lt;span class="w"&gt; &lt;/span&gt;Description&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                             &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1&lt;span class="w"&gt;          &lt;/span&gt;aliyun&lt;span class="w"&gt;             &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                             &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.2&lt;span class="w"&gt;          &lt;/span&gt;aliyun&lt;span class="w"&gt;             &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                             &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.3&lt;span class="w"&gt;          &lt;/span&gt;aliyun&lt;span class="w"&gt;             &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                             &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.4&lt;span class="w"&gt;          &lt;/span&gt;aliyun&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;add&lt;/code&gt; 和 &lt;code&gt;remove&lt;/code&gt; 子命令分别用来安装和卸载依赖，都会同步修改 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aiohttp&amp;gt;2,&amp;lt;3&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;httpx

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;aiohttp
&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt;         &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                      &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt;      &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.3.10&lt;span class="w"&gt;                                       &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;description&lt;span class="w"&gt;  &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Async&lt;span class="w"&gt; &lt;/span&gt;http&lt;span class="w"&gt; &lt;/span&gt;client/server&lt;span class="w"&gt; &lt;/span&gt;framework&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;asyncio&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code&gt;&amp;gt;&lt;/code&gt; 或者 &lt;code&gt;&amp;lt;&lt;/code&gt; 限制版本时要使用引号，因为它们在 shell 中有重定向的含义。如果指定的版本和之前不一样，Poetry 会重新安装成新指定的版本，并更新 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;上面指定了 aiohttp 的版本范围，实际安装的是符合条件的最高版本。&lt;code&gt;pyproject.toml&lt;/code&gt; 文件中也做了相应的变更。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt;  &lt;/span&gt;^dependencies&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;pyproject.toml
&lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flask (&amp;gt;=3.1.3,&amp;lt;4.0.0)&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aiohttp (&amp;gt;2,&amp;lt;3)&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;httpx (&amp;gt;=0.28.1,&amp;lt;0.29.0)&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上传项目到 Git 时，建议上传 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;poetry.lock&lt;/code&gt; 两个文件，不要上传 &lt;code&gt;.venv&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：如果开发的是库（library），而不是应用（application），则往往不需要上传 &lt;code&gt;poetry.lock&lt;/code&gt; 到代码版本管理仓库，因为库需要保持多个依赖版本的兼容性，如果写死版本，很可能会产生依赖冲突。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当把这个项目迁移到新环境或者目录时，可以使用 &lt;code&gt;poetry sync&lt;/code&gt; 解析并安装所有依赖，这个命令提供了 &lt;code&gt;--dry-run&lt;/code&gt; 选项，可以只输出操作，不实际运行。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;poetry add&lt;/code&gt; 和 &lt;code&gt;pip install&lt;/code&gt; 安装依赖时都会分析包的依赖，并解决依赖。在执行 &lt;code&gt;poetry remove&lt;/code&gt; 时，Poetry 会删除依赖的库，而 &lt;code&gt;pip uninstall&lt;/code&gt; 则不会，pip 只会删除指定库。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="_8"&gt;依赖组&lt;/h3&gt;
&lt;p&gt;有些依赖仅仅在开发阶段使用，比如 lint 工具；有些是在软件测试中用到的，比如 pytest。生产环境运行时应该保持最精简的状态和最小体积，不需要安装这些开发和测试阶段的依赖。为了区分依赖，可以新增 &lt;code&gt;dev&lt;/code&gt; 和 &lt;code&gt;test&lt;/code&gt; 两个可选（optional）依赖组，依赖组的名字可以随意指定，但建议使用有意义的词汇。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;ruff&lt;span class="w"&gt; &lt;/span&gt;autopep8
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pytest

$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-A7&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dependency-groups&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;dependency-groups&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ruff (&amp;gt;=0.15.4,&amp;lt;0.16.0)&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;autopep8 (&amp;gt;=2.3.2,&amp;lt;3.0.0)&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pytest (&amp;gt;=9.0.2,&amp;lt;10.0.0)&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从结果可以看到，&lt;code&gt;pyproject.toml&lt;/code&gt; 中多了 &lt;code&gt;[dependency-groups]&lt;/code&gt; 子表，内容是指定安装的依赖。在 &lt;code&gt;[project]&lt;/code&gt; 中 &lt;code&gt;dependencies&lt;/code&gt; 里定义的依赖是 &lt;code&gt;main&lt;/code&gt; 组。&lt;/p&gt;
&lt;p&gt;默认情况下，&lt;code&gt;install&lt;/code&gt; 命令会安装所有依赖组。使用 &lt;code&gt;--with&lt;/code&gt; 安装可选组，使用 &lt;code&gt;--without&lt;/code&gt; 排除可选组，使用 &lt;code&gt;--only&lt;/code&gt; 只安装某些组。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--only&lt;span class="o"&gt;=&lt;/span&gt;dev,main
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--without&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在目前的 &lt;code&gt;pyproject.toml&lt;/code&gt; 配置下（只有 main、dev、test 这三个组），上面两个命令的效果等价。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：使用 &lt;code&gt;--only&lt;/code&gt; 时，Poetry 会自动忽略 &lt;code&gt;--with&lt;/code&gt; 和 &lt;code&gt;--without&lt;/code&gt; 选项。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;show&lt;/code&gt; 子命令可以查看依赖的安装情况，默认会把间接依赖也列出来，使用 &lt;code&gt;--tree&lt;/code&gt; 以树形方式展示依赖关系，使用 &lt;code&gt;--top-level&lt;/code&gt; 选项只列出 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件中指定的顶级依赖。&lt;/p&gt;
&lt;h3 id="poetrylock"&gt;poetry.lock 文件&lt;/h3&gt;
&lt;p&gt;上面说过，&lt;code&gt;poetry.lock&lt;/code&gt; 文件详细记录了当前项目所有依赖包的精确版本信息，包括直接依赖和间接依赖，相当于当前项目依赖情况的一个快照。不要手动修改 &lt;code&gt;poetry.lock&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;如果手动修改了 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件的依赖项，可能会导致 &lt;code&gt;poetry.lock&lt;/code&gt; 中的依赖不一致。运行 &lt;code&gt;poetry install&lt;/code&gt; 时会报错：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;install
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="w"&gt; &lt;/span&gt;file

pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;significantly&lt;span class="w"&gt; &lt;/span&gt;since&lt;span class="w"&gt; &lt;/span&gt;poetry.lock&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="w"&gt; &lt;/span&gt;generated.&lt;span class="w"&gt; &lt;/span&gt;Run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;fix&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="w"&gt; &lt;/span&gt;file.

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;lock
Resolving&lt;span class="w"&gt; &lt;/span&gt;dependencies...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.0s&lt;span class="o"&gt;)&lt;/span&gt;

Writing&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="w"&gt; &lt;/span&gt;file
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;poetry install&lt;/code&gt; 时的输出提示 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;poetry.lock&lt;/code&gt; 文件内容不同步，需要运行 &lt;code&gt;poetry lock&lt;/code&gt; 来更新 &lt;code&gt;poetry.lock&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;code&gt;poetry lock&lt;/code&gt; 这个命令只会更新 &lt;code&gt;poetry.lock&lt;/code&gt; 文件的依赖条目，并不会安装依赖。而且这个过程会遍历所有直接依赖的依赖项，以便解决潜在的依赖版本冲突问题。&lt;/p&gt;
&lt;p&gt;依赖锁定主要解决如下两个问题：
+ 解析：找到满足所有依赖限制下的解决方案，避免出现间接依赖的版本冲突。
+ 锁定：通过 &lt;code&gt;poetry.lock&lt;/code&gt; 文件为当前所有依赖项创建快照。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 &lt;code&gt;poetry lock --regenerate&lt;/code&gt; 会忽略已有的 poetry.lock 文件，重新创建一个 poetry.lock 文件覆盖旧的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;执行 &lt;code&gt;poetry install&lt;/code&gt; 会安装 &lt;code&gt;poetry.lock&lt;/code&gt; 文件中的所有依赖，且只安装环境中缺失的那部分。&lt;/p&gt;
&lt;p&gt;如果环境中存在 &lt;code&gt;poetry.lock&lt;/code&gt; 和 &lt;code&gt;pyproject.toml&lt;/code&gt; 中未记录的依赖怎么办？比如，使用 pip 往虚拟环境中安装的依赖。使用 &lt;code&gt;poetry sync&lt;/code&gt; 不止会依赖检查、安装和同步，也会清理环境中未被记录的依赖。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;requests

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;sync
Installing&lt;span class="w"&gt; &lt;/span&gt;dependencies&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;lock&lt;span class="w"&gt; &lt;/span&gt;file

Package&lt;span class="w"&gt; &lt;/span&gt;operations:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;installs,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;updates,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;removals

&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;charset-normalizer&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.4.4&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;requests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.32.5&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;urllib3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.6.3&lt;span class="o"&gt;)&lt;/span&gt;

Installing&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;current&lt;span class="w"&gt; &lt;/span&gt;project:&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code&gt;sync&lt;/code&gt; 子命令，Poetry 移除了虚拟环境中没有被 &lt;code&gt;poetry.lock&lt;/code&gt; 文件记录的依赖，使得虚拟环境和 &lt;code&gt;poetry.lock&lt;/code&gt; 的依赖保持一致。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;poetry install&lt;/code&gt; 和 &lt;code&gt;poetry sync&lt;/code&gt; 命令的功能十分相似，都可以安装指定的依赖项，但有两点不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;poetry install&lt;/code&gt; 不会清理环境中 Poetry 未记录的依赖，而 &lt;code&gt;poetry sync&lt;/code&gt; 会清理。&lt;code&gt;sync&lt;/code&gt; 比 &lt;code&gt;install&lt;/code&gt; 更严格。&lt;/li&gt;
&lt;li&gt;如果是 package 项目，&lt;code&gt;poetry install&lt;/code&gt; 会把当前的项目作为依赖安装。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="_9"&gt;依赖升级&lt;/h3&gt;
&lt;p&gt;Poetry 的 &lt;code&gt;show&lt;/code&gt; 子指令可以查看项目依赖的情况：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;aiohttp
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;--latest&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;查看 aiohttp 包的版本和依赖关系&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--latest&lt;/code&gt; 查看依赖可用的最新版本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果依赖有满足条件的新版本，可以使用 &lt;code&gt;update&lt;/code&gt; 子命令进行更新。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;aiohttp
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上示例分别用于更新指定的依赖和更新所有依赖。&lt;code&gt;update&lt;/code&gt; 也支持 &lt;code&gt;--dry-run&lt;/code&gt; 选项，由于版本升级总是伴随冲突风险，建议升级前查看具体升级了哪些依赖。&lt;/p&gt;
&lt;p&gt;如果要突破 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件中的版本限制进行升级，比如 &lt;code&gt;aiohttp 2.3.10&lt;/code&gt; 升级到 &lt;code&gt;aiohttp 3.3.0&lt;/code&gt;，或者直接升级到最新版本，需要通过如下的 &lt;code&gt;add&lt;/code&gt; 指令操作，这个命令也会同步修改 &lt;code&gt;pyproject.toml&lt;/code&gt; 中的依赖版本限制。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aiohttp&amp;lt;3.10&amp;quot;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aiohttp (&amp;lt;3.10)&amp;quot;&lt;/span&gt;,

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt;         &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;aiohttp&lt;span class="w"&gt;                                      &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt;      &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9.5
&lt;span class="w"&gt; &lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;升级依赖版本和新增依赖一样，都是 &lt;code&gt;add&lt;/code&gt; 子命令；通过新的版本约束触发重新解析并升级到符合条件的版本。&lt;/p&gt;
&lt;h2 id="_10"&gt;虚拟环境&lt;/h2&gt;
&lt;p&gt;如果 Poetry 的全局配置项 &lt;code&gt;virtualenvs.in-project=false&lt;/code&gt;，Poetry 项目的虚拟环境会放在用户目录下的 &lt;code&gt;.cache/pypoetry/virtualenvs&lt;/code&gt;，可以为一个项目支持多个不同的 Python 环境，以便切换多个版本进行测试。当 &lt;code&gt;virtualenvs.in-project=true&lt;/code&gt;，项目的虚拟环境就是项目目录下的 &lt;code&gt;.venv&lt;/code&gt; 目录，就无法再支持多版本了。&lt;/p&gt;
&lt;p&gt;Poetry 在运行时，会通过检查 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件检测当前是否在 Poetry 环境中，是否已经有虚拟环境。如果 Poetry 项目中还没有虚拟环境，Poetry 会自动创建虚拟环境后执行命令。下面是临时把 &lt;code&gt;virtualenvs.in-project&lt;/code&gt; 改为 &lt;code&gt;false&lt;/code&gt; 之后，测试多个版本的 Python 环境。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;virtualenvs.in-project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 设置虚拟环境为用户目录下，而非项目目录下&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/^requires-python.*$/requires-python = &amp;quot;&amp;gt;=3.11&amp;quot;/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyproject.toml&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 修改项目的python依赖为 &amp;gt;= 3.11&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 切换 Python 3.11&lt;/span&gt;
Creating&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;app-X2aZMY7n-py3.11&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs
Using&lt;span class="w"&gt; &lt;/span&gt;virtualenv:&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.11
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;--version&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 验证当前 Python 版本&lt;/span&gt;
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.11.14

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 切换 Python 3.13&lt;/span&gt;
Creating&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;app-X2aZMY7n-py3.13&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs
Using&lt;span class="w"&gt; &lt;/span&gt;virtualenv:&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.13
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;--version&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 验证当前 Python 版本&lt;/span&gt;
Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.5

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt; &lt;/span&gt;--path&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 虚拟环境的路径&lt;/span&gt;
~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.13

$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;list
app-X2aZMY7n-py3.11
app-X2aZMY7n-py3.13&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Activated&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 当前启用的 Python 版本&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上命令使用 &lt;code&gt;env use&lt;/code&gt; 创建了 Python 3.11 和 Python 3.13 的虚拟环境，后一个虚拟环境 Python 3.13 目前处于 &lt;code&gt;Activated&lt;/code&gt; 状态。不同版本的虚拟环境有不同的虚拟环境目录，切换不同 Python 版本很方便验证项目在各个版本上的可用性。指定的虚拟环境版本必须符合 &lt;code&gt;pyproject.toml&lt;/code&gt; 中对 Python 依赖的规定。&lt;/p&gt;
&lt;p&gt;虚拟环境的路径中有一串看似无意义的随机字符串（X2aZMY7n），实际是对项目路径做了 hash 之后的 base64 编码，如果项目路径发生变化，虚拟环境也会重新生成。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;poetry env remove --all&lt;/code&gt; 删除所有虚拟环境，也可以将 &lt;code&gt;--all&lt;/code&gt; 替换为具体的版本，删除特定版本。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;--all&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_11"&gt;进入虚拟环境&lt;/h3&gt;
&lt;p&gt;在写代码或者某些场景中，可能需要进入虚拟环境操作，方法如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;env&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 查看激活虚拟环境的方法&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.13/bin/activate

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.13/bin/activate&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 激活虚拟环境&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;app-py3.13&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 检查虚拟环境&lt;/span&gt;
~/.cache/pypoetry/virtualenvs/app-X2aZMY7n-py3.13/bin/python

&lt;span class="o"&gt;(&lt;/span&gt;app-py3.13&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;deactivate&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 退出虚拟环境 &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;和正常激活虚拟环境的区别不大，主要是找到当前的虚拟环境路径。&lt;/p&gt;
&lt;h2 id="_12"&gt;运行测试项目&lt;/h2&gt;
&lt;h3 id="poetry_1"&gt;开发流程中的 Poetry&lt;/h3&gt;
&lt;p&gt;在使用 Poetry 管理的项目中，开发流程中常涉及的管理动作如下：
+ &lt;code&gt;poetry new&lt;/code&gt; 创建项目。
+ &lt;code&gt;poetry add/remove&lt;/code&gt; 管理项目依赖
+ 开发项目
+ &lt;code&gt;poetry run&lt;/code&gt; 运行和测试项目
+ &lt;code&gt;poetry sync&lt;/code&gt; 更新本地的依赖情况&lt;/p&gt;
&lt;p&gt;一般来说，&lt;code&gt;pyproject.toml&lt;/code&gt; 文件中的某些信息也是需要偶尔手动维护的。&lt;/p&gt;
&lt;h3 id="_13"&gt;实例&lt;/h3&gt;
&lt;p&gt;新建项目&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;simple-web&lt;span class="w"&gt; &lt;/span&gt;--flat
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;simple-web
$&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;flask
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;修改 &lt;code&gt;app/__init__.py&lt;/code&gt; 文件为如下内容，是一个最简单的 Flask Web 应用：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello, World&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;运行方法：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app:app&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_14"&gt;小结&lt;/h2&gt;
&lt;p&gt;Poetry 把依赖管理、虚拟环境与构建发布整合到统一流程中，使用门槛低、可重复性强。对应用型项目，建议优先明确依赖组与 Python 版本约束；对库项目，则更应关注依赖范围与锁定策略的边界。值得关注的是，Poetry 之外还有势头正猛的后起之秀 &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt;，将在下一篇博客中介绍。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/python-poetry.html</guid>
      <pubDate>Sun, 01 Mar 2026 22:19:29 +0800</pubDate>
    </item>
    <item>
      <title>OpenTelemetry 和 Flask</title>
      <link>http://xnow.me/programs/OpenTelemetry-and-flask.html</link>
      <description>&lt;p&gt;&lt;img alt="Harvest-and-build" src="/usr/uploads/2025/12/1766909968.2800176.png" /&gt;&lt;/p&gt;
&lt;p&gt;在上一篇文章 &lt;a href="https://xnow.me/programs/optentelemetry-introduce-and-deploy.html"&gt;OpenTelemetry 入门和部署&lt;/a&gt;  中介绍了 OpenTelemetry的架构，并部署了一套极简的OpenTelemetry服务。在上一篇文章的最后，我们使用curl向OpenTelemetry Collector服务的HTTP接口上报了Traces、Metrics和Logs数据，并进行了验证。本文将从测试场景进入到应用场景，编写两个基于Flask的Web服务，看看在Python Web开发场景中，如何将应用的Traces、Metrics和Logs数据上报到OpenTelemetry。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="Harvest-and-build" src="/usr/uploads/2025/12/1766909968.2800176.png" /&gt;&lt;/p&gt;
&lt;p&gt;在上一篇文章 &lt;a href="https://xnow.me/programs/optentelemetry-introduce-and-deploy.html"&gt;OpenTelemetry 入门和部署&lt;/a&gt;  中介绍了 OpenTelemetry的架构，并部署了一套极简的OpenTelemetry服务。在上一篇文章的最后，我们使用curl向OpenTelemetry Collector服务的HTTP接口上报了Traces、Metrics和Logs数据，并进行了验证。本文将从测试场景进入到应用场景，编写两个基于Flask的Web服务，看看在Python Web开发场景中，如何将应用的Traces、Metrics和Logs数据上报到OpenTelemetry。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;如果还不了解OpenTelemetry，请先移步上一篇文章 &lt;a href="https://xnow.me/programs/optentelemetry-introduce-and-deploy.html"&gt;OpenTelemetry 入门和部署&lt;/a&gt; ，了解OpenTelemetry的相关架构和原理。本文主要探索SDK端的用法，各个后端平台还是使用上一篇文章中搭建的OpenTelemetry服务。&lt;/p&gt;
&lt;h2 id="_1"&gt;项目初始化&lt;/h2&gt;
&lt;p&gt;OpenTelemetry的Python SDK文档：https://opentelemetry.io/docs/languages/python/getting-started/&lt;/p&gt;
&lt;p&gt;OpenTelemetry官方也提供了一个包含十几个微服务的测试项目，分别用十几种语言实现，详情参考 &lt;a href="https://opentelemetry.io/docs/demo/services/"&gt;OpenTelemetry Demo&lt;/a&gt; ，比较复杂，可以在遇到问题时查看和参考。&lt;/p&gt;
&lt;p&gt;本案例中的各个软件版本：
OS：Debian 13
Python： 3.13.5
Flask：3.1.2&lt;/p&gt;
&lt;p&gt;本测试项目受OpenTelemetry文档中的 &lt;a href="https://opentelemetry.io/docs/languages/python/getting-started/"&gt;骰子项目&lt;/a&gt; 启发，将其改造为2个HTTP服务，形成调用关系，方便我们观察跨微服务的链路跟踪。&lt;/p&gt;
&lt;p&gt;使用uv做项目管理，在项目中包含两个Python源码文件，分别是 &lt;code&gt;dice-app.py&lt;/code&gt; 和 &lt;code&gt;dice-backend.py&lt;/code&gt; ，前者通过HTTP方法调用后者，分别上报各自的指标到OpenTelemetry。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;dice
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;flask
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="dice-backend"&gt;dice-backend应用&lt;/h2&gt;
&lt;p&gt;创建一个简单的Flask应用——摇骰子，功能是在sqlite3数据库执行 &lt;code&gt;select random()&lt;/code&gt;命令，然后把取得的数字对7做取模操作，获得余数作为骰子点数，如果点数在1~6之间则返回，否则抛出&lt;code&gt;ValueError&lt;/code&gt;异常。dice-backend.py 的代码如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;meter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_meter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cnx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;:memory:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_same_thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;roll_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;    &lt;span class="c1"&gt;# 定义Counter类型的指标&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;dice_number_counter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The number of rolls by roll value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/rolldice&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;roll_dice&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;random dice number: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;roll&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;roll_span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# 手工插桩&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cnx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select random()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;roll_span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin_random_number&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 设置span的属性&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;raw number from sqlite3 is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# 在 span 上下文记录的日志会携带 trace_id 等字段&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;       &lt;span class="c1"&gt;# 骰子点数必须在1-6之间&lt;/span&gt;
        &lt;span class="n"&gt;roll_counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;roll.value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;      &lt;span class="c1"&gt;# 对metric进行设置&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dice must in (1, 6), given: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;代码的内容非常简单，特殊的地方在于手动添加了2个OpenTelemetry的数据上报点。第1个是把从sqlite3中取出的数据设置到trace的&lt;code&gt;origin_random_number&lt;/code&gt;这个属性中。第二个是metrics的统计功能，对骰子点数出现的次数进行&lt;code&gt;+1&lt;/code&gt;操作，用于统计骰子每个点数出现的次数。&lt;/p&gt;
&lt;p&gt;代码中使用 &lt;code&gt;with tracer.start_as_current_span("roll") as roll_span: ...&lt;/code&gt;做了手动插桩， 这是链路跟踪的常见操作，创建了名为&lt;code&gt;roll&lt;/code&gt;的span，span是分布式系统中单个操作的执行记录，包含开始时间、持续时间、状态和元数据等信息。在这个span的上下文内，我们为span添加了一个属性（使用set_attribute函数），记录执行过程中的信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;span是分布式链路追踪（Distributed Tracing）领域的通用术语。在分布式系统中，一次请求的完整路径被称为 Trace（追踪），而路径上的每一个逻辑节点（比如一次数据库查询、一次 RPC 调用、一次函数执行）都被形象地称为 Span（跨度）。之所以叫“跨度”，是因为它代表了：时间的跨度，从操作开始（Start Time）到结束（End Time）的时间区间；逻辑的跨度， 在复杂的拓扑图中，它是连接两个组件的“桥梁”。在SkyWalking和Zipkin等跟踪系统中均有此术语。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;另外，这个代码有个明显的bug，骰子有1/7的概率结果为0, 触发ValueError的异常。&lt;/p&gt;
&lt;p&gt;对于上报的logs数据来说，在span上下文中记录的日志在OTLP协议上报时会被附上trace_id等字段，在问题排查时，可以把日志中的trace id和链路跟踪系统中的trace id对应起来综合排查。&lt;/p&gt;
&lt;h3 id="_2"&gt;运行和测试&lt;/h3&gt;
&lt;p&gt;OpenTelemetry提供了对常见Python库的自动插桩（Instrumentation）支持，比如Flask、sqlite3、logging、httpx等，官方支持的一些Python库，见文档 &lt;a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation"&gt;Instrumentation&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;在这个项目中，我们使用OpenTelementy为Python提供的自动插桩工具，并为代码中使用到的库安装opentelemetry提供的专用SDK。&lt;/p&gt;
&lt;p&gt;安装依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;opentelemetry-instrumentation&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-distro&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-exporter-otlp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-instrumentation-sqlite3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-instrumentation-flask&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-instrumentation-logging
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;安装完依赖后使用自动插桩的方法运行dice-backend.py应用：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://localhost:4317&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dice-backend.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
opentelemetry-instrument&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--traces_exporter&lt;span class="w"&gt; &lt;/span&gt;otlp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--metrics_exporter&lt;span class="w"&gt; &lt;/span&gt;otlp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--logs_exporter&lt;span class="w"&gt; &lt;/span&gt;otlp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--service_name&lt;span class="w"&gt; &lt;/span&gt;dice-backend&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;环境变量&lt;code&gt;OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED&lt;/code&gt; 设置启用日志库的自动插桩。&lt;/li&gt;
&lt;li&gt;环境变量&lt;code&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/code&gt; 指定了OpenTelemetry的GRPC地址为本机的 &lt;code&gt;http://localhost:4317&lt;/code&gt;，也就是上一篇文章中在本地搭建的Opentelemetry服务。&lt;/li&gt;
&lt;li&gt;环境变量&lt;code&gt;FLASK_APP&lt;/code&gt;  指定了 flask项目的文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opentelemetry-instrument&lt;/code&gt;命令是自动插桩用的，参数中指定了 traces、metrics和logs的exporter类型为otlp。表示将数据上报到OpenTelemetry。其实exporter的类型很丰富，调试时也常常把exporter设置为console输出到命令行，比如 &lt;code&gt;--traces_exporter console&lt;/code&gt; ，也支持同时设置多个exporter，比如 &lt;code&gt;--traces_exporter otlp,console&lt;/code&gt;。&lt;code&gt;--service_name&lt;/code&gt; 参数指定了应用标识为&lt;code&gt;dice-backend&lt;/code&gt;，OTLP上报时被转换为 &lt;code&gt;service.name&lt;/code&gt;字段 。&lt;/li&gt;
&lt;li&gt;使用uv环境和flask run命令运行项目，并监听在5001端口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反复运行下面的curl命令对dice-backend服务进行测试&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:5001/rolldice
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;多次请求这个接口，大概有1/7的可能性会返回 &lt;code&gt;500 INTERNAL SERVER ERROR&lt;/code&gt; 报错。正常的时候应该都会返回1～6之间的数字。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开Jaeger的ui  http://127.0.0.1:16686，查看&lt;code&gt;dice-backend&lt;/code&gt;应用的traces数据， 点进细看请求的链路的树形结构，会看到3个span，分别是：&lt;ul&gt;
&lt;li&gt;对sqlite的自动插桩，内容是执行的SQL语句；&lt;/li&gt;
&lt;li&gt;对roll函数的手动插桩，内容是&lt;code&gt;select randome()&lt;/code&gt;语句的执行结果；&lt;/li&gt;
&lt;li&gt;对flask的自动插桩，记录HTTP请求参数；&lt;/li&gt;
&lt;li&gt;以上3个span有各自的span id，却有共同的trace id&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当接口报错时，在Jaeger上可以看到具体的异常报错&lt;/li&gt;
&lt;li&gt;打开Prometheus的ui http://127.0.0.1:9090，搜索 &lt;code&gt;dice_mumber_counter_total&lt;/code&gt; 指标的结果，查看骰子的每个点数出现的次数。&lt;ul&gt;
&lt;li&gt;此外，还有 http_server_* 等OpenTelemetry的flask库内置指标&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;打开Elasticsearch 的 &lt;code&gt;http://127.0.0.1:9200/logs-dice-backend/_search&lt;/code&gt;，查看上报的日志，能看到flask输出的access log。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三个平台的数据之间有一些共同的字段，比如 server.name（prometheus中一般是service_name），traces和logs中也可能有共同的trace_id。&lt;/p&gt;
&lt;p&gt;使用Opentelemetry提供的自动插桩功能就能实现对常见库调用的traces数据收集，也很方便的手动对函数做插桩，实现自定义指标数据上报。这是开发中的一种常见做法，不用对代码进行大面积改动。&lt;/p&gt;
&lt;p&gt;接下来实现dice项目的app服务，在该服务中，将完全使用代码进行opentelemetry各个exporter的初始化。&lt;/p&gt;
&lt;h2 id="dice-app"&gt;dice-app 应用&lt;/h2&gt;
&lt;p&gt;在 dice-app 服务中，将用httpx库批量调用dice-backend服务的HTTP接口，获取骰子点数，用jinja2渲染为Web页面。功能也很简单。dice-app.py文件的代码如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jinja2&lt;/span&gt;

&lt;span class="c1"&gt;##### OpenTelemetry 的初始化: traces、logs、metrices&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk.resources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVICE_VERSION&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk.trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk.trace.export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;  &lt;span class="c1"&gt;# ConsoleSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk._logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LoggerProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LoggingHandler&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk._logs.export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BatchLogRecordProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ConsoleLogExporter,&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.exporter.otlp.proto.grpc._log_exporter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPLogExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk.metrics.export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ConsoleMetricExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.sdk.metrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MeterProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.exporter.otlp.proto.grpc.metric_exporter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPMetricExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.instrumentation.flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlaskInstrumentor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.instrumentation.jinja2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Jinja2Instrumentor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.instrumentation.httpx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTTPXClientInstrumentor&lt;/span&gt;  &lt;span class="c1"&gt;# noqa&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;opentelemetry.instrumentation.logging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LoggingInstrumentor&lt;/span&gt;

&lt;span class="n"&gt;OTLP_EXPORTER_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:4317&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVICE_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;trace_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;otlp_exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OTLP_EXPORTER_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insecure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;trace_provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otlp_exporter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log_exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OTLPLogExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OTLP_EXPORTER_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insecure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger_provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_log_record_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BatchLogRecordProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_exporter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger_provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logger_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;meter_exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OTLPMetricExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OTLP_EXPORTER_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insecure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;meter_reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;meter_exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;export_interval_millis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# metrics 每15s更新&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;meter_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MeterProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metric_readers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;meter_reader&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_meter_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meter_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;meter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_meter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Jinja2Instrumentor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;HTTPXClientInstrumentor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;LoggingInstrumentor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;##### OpenTelemetry 的初始化结束&lt;/span&gt;

&lt;span class="n"&gt;access_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;access_times&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;index accesse times&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FlaskInstrumentor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 对Flask应用进行插桩&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;roll_dice_index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;get_client_ip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client_span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client_span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;client_ip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;access_counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jinja2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        {&lt;/span&gt;&lt;span class="si"&gt;% f&lt;/span&gt;&lt;span class="s2"&gt;or j in data %}&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;li&amp;gt;{{j}}&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndfor %}&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;/ul&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:5001/rolldice&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面的代码看着很多，近80行。但实现的功能挺简单的，都集中在&lt;code&gt;roll_dice_index&lt;/code&gt;这一个简单的flask视图函数中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直到&lt;code&gt;app = Flask(__name__)&lt;/code&gt; 之前，大部分的代码都是在设置OpenTelemetry指标上报相关的功能。忽略掉import语句，这部分内容主要做了如下工作：&lt;ul&gt;
&lt;li&gt;定义上报的OpenTelemetry的目标地址为 "http://127.0.0.1:4317" 。&lt;/li&gt;
&lt;li&gt;创建resource，其中的字段会作为属性一起上报给OpenTelemetry。&lt;/li&gt;
&lt;li&gt;初始化traces、logs和metrics实例。初始化步骤其实差不多，都是定义provider、定义基于GRPC的exporter、将exporter关联到provider上，将provider设置给全局的traces、metrics和logging。&lt;/li&gt;
&lt;li&gt;对于项目中用到的httpx、jinja2、logging、flask库进行自动化插桩库的启动。&lt;/li&gt;
&lt;li&gt;创建了一个名为 access_times 的counter类型指标，用于统计主页被访问的次数。&lt;/li&gt;
&lt;li&gt;创建了两个钩子函数&lt;code&gt;request_hook&lt;/code&gt; 和 &lt;code&gt;response_hook&lt;/code&gt;，分别记录用户对这个服务的请求和响应的内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;实例化Flask对象后，对flask应用进行插桩，关联了上面定义的两个钩子函数。&lt;/li&gt;
&lt;li&gt;本案例代码中的手动插桩函数在&lt;code&gt;dice-backend&lt;/code&gt; 项目中都见过了，不做详细介绍了。&lt;/li&gt;
&lt;li&gt;在完全使用代码实现的插桩方式中，也可以引入console或者file类型的exporter。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenTelemetry初始化和业务代码混在一起，上面的代码看着有点吓人。在实际的项目中，一般会将OpenTelemetry的初始化代码放到独立的模块中，避免影响业务代码的整洁度。&lt;/p&gt;
&lt;h3 id="_3"&gt;运行和测试&lt;/h3&gt;
&lt;p&gt;这个项目中用到了httpx和jinja2模块，安装相关依赖和自动插桩库。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;httpx&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-instrumentation-jinja2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;opentelemetry-instrumentation-httpx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在代码中初始化OpenTelemetry的场景下，使用常规的方法启动flask应用就行，监听到5000端口。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dice-app.py&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;flask&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;反复执行下面的命令。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:5000/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看相关的上报数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jaeger中可以看到调用链路了，总共12个span，6个来自dice-app应用，另外6个来自dice-backend（对dice-backend调用了2次，每次3个span），在dice-app中的6个span分别是：&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flask&lt;/code&gt;自动插桩，记录请求和响应；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_client_ip&lt;/code&gt; 手动插桩，记录请求者的ip地址。其实在flask的插桩中已经有了，没有什么实际价值。&lt;/li&gt;
&lt;li&gt;来自jinja2的&lt;code&gt;jinja2.compile&lt;/code&gt; 和&lt;code&gt;jinja2.render&lt;/code&gt;的函数自动插桩。&lt;/li&gt;
&lt;li&gt;2次&lt;code&gt;httpx&lt;/code&gt;的自动插桩。记录请求和响应的http_code。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Prometheus：查看指标&lt;code&gt;access_times_total&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;打开Elasticsearch 的 &lt;code&gt;http://127.0.0.1:9200/logs-dice-app/_search&lt;/code&gt;，查看上报的日志，能看到flask输出的access log。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么&lt;code&gt;dice-app&lt;/code&gt;和&lt;code&gt;dice-backend&lt;/code&gt; 应用使用同一个&lt;code&gt;trace_id&lt;/code&gt;？
因为，我们引入了httpx库的OpenTelemetry插桩，在使用httpx向dice-backend发起请求的时候，httpx在请求的header中，自动包含了trace id字段。&lt;/p&gt;
&lt;p&gt;可以看到，即使代码中没有手动添加flask、jinja2和httpx的插桩代码，因为安装了这3个库的OpenTelemetry插桩库，使得相关的trace能够自动创建和采集。非常方便。&lt;/p&gt;
&lt;p&gt;OpenTelemetry的flask 和 httpx等许多插桩库都有钩子功能（request_hook和response_hook），可以记录请求和响应的特定字段，有兴趣的查看各个OpenTelemetry的对应库的文档做详细了解。&lt;/p&gt;
&lt;h2 id="_4"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文通过2个实际的Flask案例展示在Python开发中的2种不同的OpenTelemetry接入方法，可以从中一窥OpenTelemetry提供的强大能力。开发中，记得常看看OpenTelemetry的官方和依赖库文档，往往有意想不到的收获。&lt;/p&gt;
&lt;p&gt;OpenTelemetry正变得越来越流行，OpenTelemetry的跨语言、跨框架的traces、metrics、logs采集和处理能力，能为我们的应用带来十分不错的可观测性收益。开发者可以据此构建更加透明和可控的分布式系统，提高应用的可观测性，从而更快地定位和解决问题。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/OpenTelemetry-and-flask.html</guid>
      <pubDate>Sun, 28 Dec 2025 16:21:45 +0800</pubDate>
    </item>
    <item>
      <title>OpenTelemetry 入门和部署</title>
      <link>http://xnow.me/programs/optentelemetry-introduce-and-deploy.html</link>
      <description>&lt;p&gt;&lt;img alt="building-in-the-autumn" src="/usr/uploads/2025/12/1765723610.3316555.png" /&gt;&lt;/p&gt;
&lt;p&gt;在维护现代分布式系统时，了解服务的运行状况和进行错误分析往往充满挑战。当客户请求横跨多个微服务、函数和基础设施时，如何能够清晰地洞察整个请求链路的健康状况与性能表现？这个问题的答案，就在于完善的&lt;strong&gt;服务可观测性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可观测性不仅仅是传统的监控（记录已知的故障模式），它赋予我们通过系统外部输出来&lt;strong&gt;探索、分析和理解系统内部状态&lt;/strong&gt;的能力。这通常建立在三大支柱之上：&lt;strong&gt;指标（Metrics）&lt;/strong&gt;、&lt;strong&gt;链路（Traces）&lt;/strong&gt; 和&lt;strong&gt;日志（Logs）&lt;/strong&gt;。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="building-in-the-autumn" src="/usr/uploads/2025/12/1765723610.3316555.png" /&gt;&lt;/p&gt;
&lt;p&gt;在维护现代分布式系统时，了解服务的运行状况和进行错误分析往往充满挑战。当客户请求横跨多个微服务、函数和基础设施时，如何能够清晰地洞察整个请求链路的健康状况与性能表现？这个问题的答案，就在于完善的&lt;strong&gt;服务可观测性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可观测性不仅仅是传统的监控（记录已知的故障模式），它赋予我们通过系统外部输出来&lt;strong&gt;探索、分析和理解系统内部状态&lt;/strong&gt;的能力。这通常建立在三大支柱之上：&lt;strong&gt;指标（Metrics）&lt;/strong&gt;、&lt;strong&gt;链路（Traces）&lt;/strong&gt; 和&lt;strong&gt;日志（Logs）&lt;/strong&gt;。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2 id="opentelemetry"&gt;OpenTelemetry简介&lt;/h2&gt;
&lt;p&gt;在传统的可观测性实践中，应用程序需要分别使用不同的SDK对接各种监控平台，如Prometheus用于指标监控、Jaeger用于链路跟踪、Elasticsearch用于日志处理。在异构技术栈并存的微服务环境中，如何以一种统一、标准化的方式采集和关联这些运行数据，是一个复杂的难题。正是在这样的背景下，&lt;strong&gt;OpenTelemetry（简称OTel）&lt;/strong&gt; 应运而生。它是一个由云原生计算基金会（CNCF）托管的开源项目，旨在提供一套与供应商无关的、统一的API、SDK和工具集，用于采集和导出遥测数据。&lt;/p&gt;
&lt;p&gt;使用OpenTelemetry，实现可观测性目标的过程被大大简化。应用程序只需要对接OpenTelemetry，SDK将指标发送给OpenTelemetry，然后OpenTelemetry会负责进行数据处理，并转发给Prometheus、Jaeger和Elasticsearch等服务。OpenTelemetry的主要目标之一就是让任何编程语言、基础设施和运行环境中的应用程序和系统都易于进行观测。&lt;/p&gt;
&lt;p&gt;具体来说，OpenTelemetry解决了3个核心问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定义一套标准——OTLP协议&lt;/strong&gt;：OpenTelemetry Protocol (OTLP) 是OpenTelemetry项目定义的一种通用遥测数据传递协议。OTLP旨在为遥测数据（包括Traces、Metrics和Logs）的编码、传输和传递提供一个标准化的机制。它定义了数据从遥测源（如应用程序的SDK）到中间节点（如OpenTelemetry Collector收集器），再到最终遥测后端（如存储和分析系统）的整个过程，实现了标准化、高效性和通用性的metrics、traces、logs数据传递。OTLP推荐并主要使用Protocol Buffers (Protobuf)进行数据的序列化和gRPC传输，也支持其他序列化和HTTP传输方式。OTLP是OpenTelemetry生态系统的"通用语言"，它通过定义标准的数据格式和传输机制，极大地简化了可观测性数据的采集和互操作性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;适配不同编程语言和框架的SDK&lt;/strong&gt;：OpenTelemetry为各种主流编程语言（如Java、Go、Python、JavaScript等）提供了官方实现的SDK。开发者只需使用对应语言的SDK进行简单集成，就能自动或手动地从应用程序中采集遥测数据，并将其转换成符合OTLP标准的格式。这极大地简化了开发者的接入成本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;指标采集和处理服务——OpenTelemetry Collector&lt;/strong&gt;：这是一个独立运行的代理服务，是OpenTelemetry架构中的"智能枢纽"。应用程序将OTLP格式的数据发送到Collector，由它来统一负责后续繁重的工作，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;接收与转发&lt;/strong&gt;：接收来自多个应用的数据，然后批量转发给一个或多个后端平台。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理与加工&lt;/strong&gt;：对数据进行清洗、过滤、采样或富化（添加额外的标签信息）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;格式转换&lt;/strong&gt;：将OTLP数据转换成其他后端系统支持的格式（如Jaeger的格式、Prometheus的格式等）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于以上三点，OpenTelemetry构建了一个标准化的、端到端的、高性能的遥测数据采集与处理管道。&lt;/p&gt;
&lt;p&gt;下图展示了OpenTelemetry的可观测性架构：&lt;/p&gt;
&lt;p&gt;&lt;img alt="OpenTelemetry Arch" src="/usr/uploads/2025/12/1765723793.2822654.png" /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，OpenTelemetry提供了各类语言的SDK用于对接OpenTelemetry，也提供了中间用于数据处理和转发的OpenTelemetry Collector工具。而数据最终的归属，依旧是大家常用的那些Elasticsearch和Prometheus等平台。OpenTelemetry就像一座桥梁，左边连接应用软件，右边连接各个可观测平台，它提供的统一SDK和数据处理工具大大降低了对接的复杂性。&lt;/p&gt;
&lt;p&gt;接下来我们将搭建一套极简版本的OpenTelemetry架构，基于以下的基础环境：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OS：Debian 13&lt;/li&gt;
&lt;li&gt;Docker：docker-ce 29.1.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="opentelemetry_1"&gt;OpenTelemetry部署&lt;/h2&gt;
&lt;p&gt;接下来使用Docker Compose部署一套基本可用的OpenTelemetry服务，包括以下4个服务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenTelemetry Collector：接收应用程序发送过来的链路跟踪、指标和日志数据等，并处理和转发到各个后端服务。&lt;/li&gt;
&lt;li&gt;Jaeger：2.12.0版本，接收OpenTelemetry Collector发送过来的OTLP标准的跟踪数据，提供链路跟踪和可视化查询能力。&lt;/li&gt;
&lt;li&gt;Prometheus：接收OpenTelemetry Collector发送过来的metrics，提供指标存储和查询的能力。&lt;/li&gt;
&lt;li&gt;Elasticsearch：8.19.7版本，作为存储后端，本案例中有两个作用，一方面用于保存OpenTelemetry Collector发送过来的日志，另一方面作为Jaeger的后端存储服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文使用了比较常见的技术栈，实际上，在OpenTelemetry的生态系统中，有大量支持OTLP协议的服务可以替换上述各组件。此处不再一一赘述，详细清单可参见官方文档。&lt;/p&gt;
&lt;h3 id="docker-compose"&gt;Docker Compose配置&lt;/h3&gt;
&lt;p&gt;为了简化部署，本文使用Docker Compose的方式部署4个服务。compose.yml的内容如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jaegertracing/jaeger:2.12.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;jaeger&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--config=file:/etc/jaeger/jaeger.yml&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./conf/jaeger.yml:/etc/jaeger/jaeger.yml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;16686:16686&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# HTTP UI&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;14317:4317&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# GRPC&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;14318:4318&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# HTTP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service_healthy&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;prometheus&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;prom/prometheus&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1000:1000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--web.enable-otlp-receiver&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--config.file=/etc/prometheus/prometheus.yml&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--storage.tsdb.path=/prometheus/data/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./conf/prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./prom_data/:/prometheus/data/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;9090:9090&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elasticsearch:8.19.7&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elasticsearch&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;always&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster.name=elasticsearch&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ES_JAVA_OPTS=-Xms1g&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-Xmx1g&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;discovery.type=single-node&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xpack.security.enabled=false&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xpack.security.enrollment.enabled=false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;9200:9200&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./es_data:/usr/share/elasticsearch/data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5s&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;60&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;curl --fail -s -o /dev/null http://127.0.0.1:9200/&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;otel-collector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.142.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;otel-collector&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;--config=/etc/otelcol-config.yml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./conf/otelcol-config.yml:/etc/otelcol-config.yml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;4317:4317&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# GRPC&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;4318:4318&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# HTTP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service_healthy&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service_started&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面的compose.yml文件看起来较长，但仔细观察会发现每个容器的配置都十分精简且易于理解，建议仔细阅读。&lt;/p&gt;
&lt;h3 id="jaeger"&gt;Jaeger配置解释&lt;/h3&gt;
&lt;p&gt;在上面定义的compose.yml中，Jaeger项目作为一个trace后端，接收trace类型的遥测数据，并提供对这些数据的处理、聚合、数据挖掘和可视化功能。&lt;/p&gt;
&lt;p&gt;容器功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;容器内监听&lt;code&gt;4317&lt;/code&gt;和&lt;code&gt;4318&lt;/code&gt;端口，映射到宿主机的&lt;code&gt;14317&lt;/code&gt;和&lt;code&gt;14318&lt;/code&gt;，避免与otel-collector监听的端口产生冲突。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;16686&lt;/code&gt;端口提供Jaeger的Web UI服务，用于查看应用的trace链路。&lt;/li&gt;
&lt;li&gt;Jaeger会将trace数据保存到Elasticsearch服务中，所以需要依赖Elasticsearch先启动。&lt;/li&gt;
&lt;li&gt;本地的&lt;code&gt;conf/jaeger.yml&lt;/code&gt;文件保存Jaeger的配置文件，里面定义了数据的接收和发送方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;conf/jaeger.yml&lt;/code&gt;配置文件的内容如下，基于Jaeger v2版本：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jaeger_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;traces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;myes_storage&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;myes_storage&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/jaeger/ui&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jaeger_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;backends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;myes_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;elasticsearch_config&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;server_urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http://elasticsearch:9200&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;index_prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jaeger-main&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;metric_backends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;myes_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;*elasticsearch_config&lt;/span&gt;

&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0.0.0:4317&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0.0.0:4318&lt;/span&gt;

&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jaeger_storage_exporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;trace_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;myes_storage&lt;/span&gt;

&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;jaeger_storage&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jaeger_query&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;traces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;jaeger_storage_exporter&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extensions:&lt;/code&gt; 定义了&lt;code&gt;jaeger_query&lt;/code&gt;和&lt;code&gt;jaeger_storage&lt;/code&gt;两个扩展，前者配置了Jaeger的Web UI功能，后者是Jaeger的存储功能。存储功能中又配置了trace存储和metric存储，都使用Elasticsearch服务保存。在myes_storage中配置了Elasticsearch服务的API地址和使用的indices名字前缀。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;receivers:&lt;/code&gt; 指定Jaeger作为接收者，监听gRPC和HTTP端口。otel-collector和应用可以把trace数据发往这两个端口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processors:&lt;/code&gt; Jaeger具有处理指标的能力，这里仅配置了常用的batch处理器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exporters:&lt;/code&gt; 定义了将数据发送到Elasticsearch存储服务中。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service:&lt;/code&gt; 引用了&lt;code&gt;extensions&lt;/code&gt;、&lt;code&gt;receivers&lt;/code&gt;、&lt;code&gt;processors&lt;/code&gt;、&lt;code&gt;exporters&lt;/code&gt;的配置。重要的是&lt;code&gt;pipelines&lt;/code&gt;中的管道，配置了从&lt;code&gt;OTLP&lt;/code&gt;协议接收traces数据，发送到&lt;code&gt;exporter&lt;/code&gt;配置中定义的Elasticsearch存储服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面配置的基本含义是：通过OTLP协议在4317 (gRPC) 和4318 (HTTP) 端口接收trace数据，使用批处理器对数据进行批量处理，通过&lt;code&gt;exporter&lt;/code&gt;将数据存储到Elasticsearch，Jaeger Query扩展从相同的Elasticsearch后端读取数据并在Web UI展示。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Jaeger的OTLP端口和OpenTelemetry Collector一样，在同一个主机上监听相同的端口号会有冲突。如果修改Jaeger的端口号，会导致Jaeger的自身指标采集报错，虽然可以用环境变量&lt;code&gt;OTEL_TRACES_SAMPLER=always_off&lt;/code&gt;禁用Jaeger的自监控，但是不建议这么做。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jaeger的官方文档内容相对较少：https://www.jaegertracing.io/docs/2.12/
Jaeger仓库中的参考配置案例：https://github.com/jaegertracing/jaeger/tree/v2.12.0/cmd/jaeger&lt;/p&gt;
&lt;h3 id="prometheus"&gt;Prometheus配置解释&lt;/h3&gt;
&lt;p&gt;Prometheus一般使用Pull模式拉取指标，但它也支持作为OTLP接收器。打开CLI标志&lt;code&gt;--web.enable-otlp-receiver&lt;/code&gt;后，就可以在Prometheus的&lt;code&gt;/api/v1/otlp/v1/metrics&lt;/code&gt;路径上提供OTLP指标接收服务。&lt;/p&gt;
&lt;p&gt;在Prometheus的Docker定义中做了如下配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启用&lt;code&gt;--web.enable-otlp-receiver&lt;/code&gt; CLI flag，开启Prometheus的OTLP指标接收功能。OpenTelemetry Collector会往这个服务发送metrics数据。&lt;/li&gt;
&lt;li&gt;另外将本地的&lt;code&gt;conf/prometheus.yml&lt;/code&gt;配置文件和&lt;code&gt;prom_data&lt;/code&gt;目录映射到容器中，映射&lt;code&gt;prom_data&lt;/code&gt;目录用于提供数据持久化的能力。&lt;/li&gt;
&lt;li&gt;Prometheus的9090端口提供接收指标和UI查询功能，暴露到主机上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prometheus配置文件&lt;code&gt;conf/prometheus.yml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;scrape_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;15s&lt;/span&gt;

&lt;span class="nt"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;keep_identifying_resource_attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;promote_resource_attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service.instance.id&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service.name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service.namespace&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service.version&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;deployment.environment.name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Prometheus的配置相对简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置OTLP接收器处理来自应用程序的遥测数据&lt;/li&gt;
&lt;li&gt;将&lt;code&gt;service.name&lt;/code&gt;、&lt;code&gt;service.instance.id&lt;/code&gt;等资源级别的属性提升为指标标签，便于查询和筛选。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prometheus一般作为指标存储服务，常与Grafana可视化平台搭配使用，简单起见，本文不涉及Grafana的内容。&lt;/p&gt;
&lt;h3 id="elasticsearch"&gt;Elasticsearch配置解释&lt;/h3&gt;
&lt;p&gt;Elasticsearch提供日志存储和查询功能，可视化的查询服务需要部署Kibana。关于Kibana服务的内容，在之前的文章&lt;a href="https://xnow.me/ops/efk-tutorial.html"&gt;EFK日志体系快速入门&lt;/a&gt;中已经有所提及，为了简化演示，本次就不部署Kibana了。如果部署Kibana，要保持Kibana和Elasticsearch的版本一致。&lt;/p&gt;
&lt;p&gt;由于&lt;a href="https://www.jaegertracing.io/docs/2.12/storage/elasticsearch/"&gt;Jaeger官方文档&lt;/a&gt;指定支持Elasticsearch的7.x和8.x版本，所以没有使用最新的Elasticsearch 9.x版本。&lt;/p&gt;
&lt;p&gt;在compose.yml文件中，对Elasticsearch做了如下的简单配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关闭了Elasticsearch安全功能，免去配置用户名和密码的麻烦。这种操作仅限于测试环境。&lt;/li&gt;
&lt;li&gt;Elasticsearch的数据映射到&lt;code&gt;es_data&lt;/code&gt;目录下，实现存储数据持久化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="opentelemetry-collector"&gt;OpenTelemetry Collector配置详解&lt;/h3&gt;
&lt;p&gt;OpenTelemetry Collector的Docker相对简单，主要是将接收数据的端口4317（gRPC）和4318（HTTP）暴露出来用于应用上报数据。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;conf/otelcol-config.yml&lt;/code&gt;配置文件的内容如下，定义了数据接收、处理和转发的逻辑：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0.0.0:4317&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0.0.0:4318&lt;/span&gt;

&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2s&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;send_batch_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1000&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;log_statements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;statements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;set(log.attributes[&amp;quot;elasticsearch.index&amp;quot;], Concat([&amp;quot;logs&amp;quot;, resource.attributes[&amp;quot;service.name&amp;quot;]], &amp;quot;-&amp;quot;))&lt;/span&gt;

&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;jaeger:4317&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;otlphttp/prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://prometheus:9090/api/v1/otlp&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://elasticsearch:9200&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;

&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;traces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlphttp/prometheus&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;elasticsearch&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;otel-collector的配置文件和Jaeger有相似的结构，这套配置文件中定义了如下3个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;receivers：otel-collector监听的gRPC和HTTP服务端口，应用程序可以往这些端口上发送数据。&lt;/li&gt;
&lt;li&gt;processors：创建了batch和transform 2个processor：&lt;ul&gt;
&lt;li&gt;batch：批量处理数据，提升处理效率。&lt;/li&gt;
&lt;li&gt;transform：为日志数据新增了&lt;code&gt;elasticsearch.index&lt;/code&gt; 这个属性，格式是 &lt;code&gt;logs-${service.name}&lt;/code&gt;，&lt;code&gt;service.name&lt;/code&gt;是客户端上报的属性名字。Elasticsearch会根据 &lt;code&gt;elasticsearch.index&lt;/code&gt;的值自动创建索引。如此，就实现了不同应用使用不同索引的需求。&lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor"&gt;transform proccessor&lt;/a&gt; 十分有用，可以很方便对日志元数据和内容进行编辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;exporters：otel-collector将把数据发往这些后端服务，分别是Jaeger、Prometheus和Elasticsearch服务。&lt;/li&gt;
&lt;li&gt;service：定义了数据处理的pipeline，trace类型的指标发往Jaeger、metric的指标发往Prometheus、日志类型的数据发往Elasticsearch。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/collector/components/processor/"&gt;OpenTelemetry Processor 文档&lt;/a&gt; 中可以查看所有的各类processor。有需要的请前往了解。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note：Elasticsearch Exporter默认使用 Data Stream 将数据发送到Elasticsearch，Data Stream 类型的索引的前缀一般是 &lt;code&gt;.ds-&lt;/code&gt; ，十分合适存储只会append数据的存储场景。使用Data Stream类型时，发送给Elasticsearch的索引（即elasticsearch.index属性）必须是 &lt;code&gt;logs-&lt;/code&gt;、&lt;code&gt;metrics-&lt;/code&gt;或者&lt;code&gt;traces-&lt;/code&gt;为prefix的，否则 Elasticsearch会返回报错&lt;code&gt;index_not_found_exception&lt;/code&gt;。如果要自定义非Data Stream 格式的索引名字，必须显式的关闭Data Stream，方法为 &lt;code&gt;elasticsearch.mapping::mode = none&lt;/code&gt;。这个地方踩了很久的坑，有太多隐藏的约定，值得注意。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;数据的处理和存储形态可能是多种多样的，比如可能有Kafka消息队列应对大规模并发；也可能仅使用Elasticsearch技术栈，毕竟Elasticsearch+Kibana几乎可以承担所有的Trace、Metric和Log的存储、处理和展示。这里展现的只是一种常见的组合方式，而OpenTelemetry的强大之处也在于对各类平台的广泛支持。&lt;/p&gt;
&lt;p&gt;另外，为了叙述方便，文中的Docker和各平台的配置都做了极简化的处理，能运行起来。但省略了安全、性能等方面的优化，在正式使用时，还需要再读读文档，对配置进行与环境适配性的改造。&lt;/p&gt;
&lt;h2 id="_1"&gt;启动服务&lt;/h2&gt;
&lt;p&gt;确保上面的&lt;code&gt;compose.yml&lt;/code&gt;和&lt;code&gt;conf/jaeger.yml&lt;/code&gt;、&lt;code&gt;conf/prometheus.yml&lt;/code&gt;、&lt;code&gt;conf/otelcol-config.yml&lt;/code&gt;文件已经都创建好了。&lt;/p&gt;
&lt;p&gt;启动前先创建Elasticsearch和Prometheus的数据持久化目录：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;es_data
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;prom_data
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;:1000&lt;span class="w"&gt; &lt;/span&gt;es_data&lt;span class="w"&gt; &lt;/span&gt;prom_data

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_2"&gt;测试验证&lt;/h2&gt;
&lt;p&gt;虽然现在还没有开发上报数据的应用，但是Jaeger自身的指标也会被采集和发送到OpenTelemetry Collector，打开Jaeger的Web UI界面，多刷新几次就能看到Jaeger自身的trace数据了。&lt;/p&gt;
&lt;p&gt;接下来使用curl命令向OpenTelemetry Collector的HTTP端口发送各类简单的测试数据。&lt;/p&gt;
&lt;h3 id="traces"&gt;测试Traces功能&lt;/h3&gt;
&lt;p&gt;向otel-collector上报一条trace数据，构造一条模拟耗时10s的trace数据（startTimeUnixNano和endTimeUnixNano相差10s）：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;http://localhost:4318/v1/traces&lt;span class="w"&gt;   &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;resourceSpans&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;resource&amp;quot;: {&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;attributes&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;key&amp;quot;: &amp;quot;service.name&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;value&amp;quot;: {&amp;quot;stringValue&amp;quot;: &amp;quot;test-service&amp;quot;}&lt;/span&gt;
&lt;span class="s1"&gt;      }]&lt;/span&gt;
&lt;span class="s1"&gt;    },&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;scopeSpans&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;spans&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;traceId&amp;quot;: &amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;rand&lt;span class="w"&gt; &lt;/span&gt;-hex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;spanId&amp;quot;: &amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;rand&lt;span class="w"&gt; &lt;/span&gt;-hex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;name&amp;quot;: &amp;quot;curl-test-span&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;kind&amp;quot;: 1,                                    &lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;startTimeUnixNano&amp;quot;: &amp;#39;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;--date&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;10 seconds ago&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+%s%N&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;endTimeUnixNano&amp;quot;: &amp;#39;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+%s%N&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;attributes&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;key&amp;quot;: &amp;quot;test.attribute&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;value&amp;quot;: {&amp;quot;stringValue&amp;quot;: &amp;quot;42&amp;quot;}&lt;/span&gt;
&lt;span class="s1"&gt;        }]&lt;/span&gt;
&lt;span class="s1"&gt;      }]&lt;/span&gt;
&lt;span class="s1"&gt;    }]&lt;/span&gt;
&lt;span class="s1"&gt;  }]&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;打开Jaeger的Web UI地址为http://127.0.0.1:16686，在打开页面的&lt;code&gt;Service&lt;/code&gt;下拉框中应该能选中&lt;code&gt;test-service&lt;/code&gt;，然后点击&lt;code&gt;Find Traces&lt;/code&gt;按钮，右侧就能看到刚刚上报的trace的详情了。其中的duration显示为10s。&lt;/p&gt;
&lt;h3 id="metrics"&gt;测试Metrics功能&lt;/h3&gt;
&lt;p&gt;向otel-collector上报一条测试的metric数据：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;http://localhost:4318/v1/metrics&lt;span class="w"&gt;   &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;resourceMetrics&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;resource&amp;quot;: {&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;attributes&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;key&amp;quot;: &amp;quot;service.name&amp;quot;, &lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;value&amp;quot;: {&amp;quot;stringValue&amp;quot;: &amp;quot;test-service&amp;quot;}&lt;/span&gt;
&lt;span class="s1"&gt;      }]&lt;/span&gt;
&lt;span class="s1"&gt;    },&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;scopeMetrics&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;metrics&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;name&amp;quot;: &amp;quot;curl_test_gauge&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;gauge&amp;quot;: {&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;dataPoints&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;quot;timeUnixNano&amp;quot;: &amp;#39;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+%s%N&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;quot;asInt&amp;quot;: &amp;quot;42&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;          }]&lt;/span&gt;
&lt;span class="s1"&gt;        }&lt;/span&gt;
&lt;span class="s1"&gt;      }]&lt;/span&gt;
&lt;span class="s1"&gt;    }]&lt;/span&gt;
&lt;span class="s1"&gt;  }]&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;打开Prometheus的Web UI，本机地址为http://127.0.0.1:9090，在输入框中填入指标名&lt;code&gt;curl_test_gauge&lt;/code&gt;并搜索，就能看到指标了。提交数据中的属性&lt;code&gt;service.name&lt;/code&gt;在Prometheus中对应了&lt;code&gt;service__name="test-service"&lt;/code&gt; label。&lt;/p&gt;
&lt;h3 id="logs"&gt;测试Logs功能&lt;/h3&gt;
&lt;p&gt;向otel-collector上报一条日志数据：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;http://localhost:4318/v1/logs&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;resourceLogs&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;resource&amp;quot;: {&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;attributes&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;key&amp;quot;: &amp;quot;service.name&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;value&amp;quot;: { &amp;quot;stringValue&amp;quot;: &amp;quot;test-service&amp;quot; }&lt;/span&gt;
&lt;span class="s1"&gt;        }]&lt;/span&gt;
&lt;span class="s1"&gt;      },&lt;/span&gt;
&lt;span class="s1"&gt;      &amp;quot;scopeLogs&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;logRecords&amp;quot;: [{&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;timeUnixNano&amp;quot;: &amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+%s%N&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;severityText&amp;quot;: &amp;quot;ERROR&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;          &amp;quot;body&amp;quot;: { &amp;quot;stringValue&amp;quot;: &amp;quot;error occurred&amp;quot; }&lt;/span&gt;
&lt;span class="s1"&gt;        }]&lt;/span&gt;
&lt;span class="s1"&gt;      }]&lt;/span&gt;
&lt;span class="s1"&gt;    }]&lt;/span&gt;
&lt;span class="s1"&gt;  }&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看Elasticsearch的indices和数据，省略了其中uuid的信息：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:9200/_cat/indices?v&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 查看索引&lt;/span&gt;
health&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;index&lt;span class="w"&gt;                                     &lt;/span&gt;uuid&lt;span class="w"&gt; &lt;/span&gt;pri&lt;span class="w"&gt; &lt;/span&gt;rep&lt;span class="w"&gt; &lt;/span&gt;docs.count&lt;span class="w"&gt; &lt;/span&gt;docs.deleted&lt;span class="w"&gt; &lt;/span&gt;store.size&lt;span class="w"&gt; &lt;/span&gt;pri.store.size&lt;span class="w"&gt; &lt;/span&gt;dataset.size
yellow&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt;   &lt;/span&gt;jaeger-main-jaeger-service-2025-12-14&lt;span class="w"&gt;     &lt;/span&gt;...&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;.9kb&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;.9kb&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;.9kb
yellow&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt;   &lt;/span&gt;.ds-logs-test-service-2025.12.14-000001&lt;span class="w"&gt;   &lt;/span&gt;...&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.8kb&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.8kb&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.8kb
yellow&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt;   &lt;/span&gt;jaeger-main-jaeger-span-2025-12-14&lt;span class="w"&gt;        &lt;/span&gt;...&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;335&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;208&lt;/span&gt;.4kb&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;208&lt;/span&gt;.4kb&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;208&lt;/span&gt;.4kb

$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/logs-test-service/_search&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 查看index中的日志&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看indices时，&lt;code&gt;jaeger-main-*&lt;/code&gt;格式的index是Jaeger发送过来的trace数据。&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;.ds-logs-test-service.*&lt;/code&gt;格式的是应用通过  otel-collector发送的日志数据，其中的doc数量是1，就是我们刚刚上报的一条日志。&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;proccessor.transform&lt;/code&gt;中设置的&lt;code&gt;elasticsearch.index&lt;/code&gt; 明明是 &lt;code&gt;logs-${service.name}&lt;/code&gt;，现在为什么变成了 &lt;code&gt;.ds-logs-test-service.*&lt;/code&gt; ？这个index名字可能有点奇怪，原因在于 &lt;code&gt;logs-${service.name}&lt;/code&gt;的index格式匹配了Elasticsearch的内置模板，名字为 &lt;code&gt;logs&lt;/code&gt;，会导致所有 &lt;code&gt;logs-*-*&lt;/code&gt;的索引都保存为 Data Stream格式，并且添加日期后缀。可以通过Elasticsearch的 &lt;code&gt;/_index_template/logs&lt;/code&gt;接口查看这个模板的信息。&lt;/p&gt;
&lt;p&gt;具体可以参考 &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/elasticsearchexporter"&gt;Elasticsearch Exporter&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;到此为止，OpenTelemetry的各个组件部署和测试就完成了。&lt;/p&gt;
&lt;h3 id="elasticsearchdata-stream"&gt;Elasticsearch的Data Stream&lt;/h3&gt;
&lt;p&gt;在测试OpenTelemetry对接Elasticsearch是才第一次遇到Data Stream，解决了许多问题，这里对Data Stream做个介绍。&lt;/p&gt;
&lt;p&gt;Data Stream在Elasticsearch 7.9版本中引入，用于优化时序数据(如Logs、Traces和Metrics）的存储，这类数据的特点是经常append，但是不会update、insert。Data Stream因此能更高效的处理时间范围过滤。当我们创建 &lt;code&gt;logs-test-service&lt;/code&gt;这个Data Stream时，Elasticsearch会自动创建索引&lt;code&gt;.ds-logs-test-service-2025.12.14-000001&lt;/code&gt;。这个索引的各字段含义为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.ds-&lt;/code&gt;：固定前缀，表示这是 Data Stream 的后端索引，一个Data Stream索引可能由多个这样的后端索引构成，分散的存储数据。&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt;开头的索引被视为&lt;code&gt;隐藏索引（Hidden Indices）&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;不要手动向&lt;code&gt;.ds-&lt;/code&gt;索引进行数据增删改操作。不要手动删除&lt;code&gt;.ds-&lt;/code&gt;索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.ds-&lt;/code&gt;索引应该由&lt;code&gt;ILM（索引生命周期管理）&lt;/code&gt;自动化管理，&lt;code&gt;ILM&lt;/code&gt;可以通过&lt;code&gt;/_ilm/policy/logs&lt;/code&gt;查看。&lt;/li&gt;
&lt;li&gt;用户应该始终使用&lt;code&gt;logs-test-service&lt;/code&gt;，而不是使用&lt;code&gt;.ds-logs-test-service-*&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logs-test-service&lt;/code&gt;：定义的 Data Stream 名字。Data Stream 不是独立存在的，它必须匹配一个“开启了 Data Stream 功能”的索引模板。比如&lt;code&gt;logs-test-service&lt;/code&gt;就符合&lt;code&gt;logs&lt;/code&gt;索引模板的 pattern (logs-&lt;em&gt;-&lt;/em&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025.12.14&lt;/code&gt;：该索引创建时的日期。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;000001&lt;/code&gt;：代际号（Generation）。这是一个 6 位填充的数字，从 000001 开始，每当发生一次 Rollover（滚动），这个数字就会加 1。rollover的时机可能是index的大小或者文档数量达到设定的阈值了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是和 Data Stream相关的一些操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GET /_data_stream&lt;/strong&gt;：查看所有数据流&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GET /_data_stream/logs-test-service&lt;/strong&gt;：查看特定数据流的详细信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GET /_data_stream/_stats&lt;/strong&gt;：查看数据流统计信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST /logs-test-service/_rollover&lt;/strong&gt;：手动rollver，产生新的后端索引&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，每次往data stream中写入数据时，必须指定为create index操作。比如&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:9200/_bulk?pretty&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;create&amp;quot;:{&amp;quot;_index&amp;quot;:&amp;quot;logs-test-service&amp;quot;}}&lt;/span&gt;
&lt;span class="s1"&gt;{&amp;quot;body&amp;quot;:{&amp;quot;text&amp;quot;:&amp;quot;error occurred&amp;quot;}}&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Data Stream还有一个好处，在Kibana上创建data view时，可以直接指定 Data Stream的名字&lt;code&gt;logs-test-service&lt;/code&gt;，而不需要使用 &lt;code&gt;logs-test-service-*&lt;/code&gt;这种传统的索引匹配方法。&lt;/p&gt;
&lt;h2 id="_3"&gt;小结&lt;/h2&gt;
&lt;p&gt;本文中，我们首先介绍了OpenTelemetry通过制定标准、统一接入的方式，优化了可观测性方面的难题。然后，使用Docker部署了一套常见的OpenTelemetry架构。最后，我们使用curl工具向OpenTelemetry Collector的HTTP端口分别上报Traces、Metrics和日志数据，并进行逐一验证。&lt;/p&gt;
&lt;p&gt;为了方便展示，文中的配置都是极简的，仅限于测试，&lt;strong&gt;在真正用于生产环境之时，以上所有的配置文件，甚至整体架构方面都有大量可以优化的地方。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在下一篇文章中，将使用Python的Flask框架，分享如何在应用程序中集成OpenTelemetry的SDK，并向OpenTelemetry Collector上报数据，最终展示到各个可观测平台。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/optentelemetry-introduce-and-deploy.html</guid>
      <pubDate>Sun, 14 Dec 2025 22:51:12 +0800</pubDate>
    </item>
    <item>
      <title>我的Linux</title>
      <link>http://xnow.me/linuxes/my-linux.html</link>
      <description>&lt;p&gt;&lt;img alt="building" src="/usr/uploads/2025/11/1763811339.9327161.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;2025国庆节，给我的笔记本重装了Linux桌面系统，用上了Debian 13（代号Trixie），默认的桌面环境Gnome 48，本文趁机回顾下这十几年使用Linux桌面的历史，这是一段充满探索、适应与发现的旅程。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="building" src="/usr/uploads/2025/11/1763811339.9327161.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;2025国庆节，给我的笔记本重装了Linux桌面系统，用上了Debian 13（代号Trixie），默认的桌面环境Gnome 48，本文趁机回顾下这十几年使用Linux桌面的历史，这是一段充满探索、适应与发现的旅程。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3 id="ubuntu-gnome"&gt;起点：Ubuntu 和 Gnome&lt;/h3&gt;
&lt;p&gt;2010年，为了学习Linux，我开始把Ubuntu作为日常系统，刚开始是Windows7 + Ubuntu 双系统，没过多久，Windows就安装到虚拟机里了。那时候还是Gnome 2，它的桌面布局很好，可定制性也强，稍微做下配置效果就非常酷。Gnome2绝对是一代经典。&lt;/p&gt;
&lt;p&gt;再后来，Gnome 3出现了，在社区中引发了巨大的争议和分歧，并促使诞生了一些替代桌面环境。Gnome3的布局、设计等方面都不太适应。没过多久，Ubuntu也研发了它自己的Unity桌面，但比Gnome 3更难用。在没有更好选择的当时，还是继续先用着Gnome3。&lt;/p&gt;
&lt;p&gt;&lt;img alt="gnome3" src="/usr/uploads/2025/11/1763810327.956946.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2012年，用上了Gnome 3的桌面，兼容性还比较差，panel上大部分应用icon似乎都无法正常工作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="elementary-os"&gt;Elementary OS&lt;/h3&gt;
&lt;p&gt;在Gnome 3上坚持几年之后，发现了号称“最美Linux发行版”的Elementary OS，桌面效果细腻有质感，布局和gnome 2颇像，风格清新、简洁。试用之后就切换到当时版本代号Luna的Elementary OS。&lt;/p&gt;
&lt;p&gt;虽然Elementary OS的设计和审美一流，比当时的Gnome 3更好用，但是我认为他们花费了太多精力在自研和打磨基础桌面工具（比如浏览器、邮件客户端、日历等等），而不是整合已有的优秀开源软件。不过，这也不是什么大问题。每次激动的安装新版本时候，总还会捐一点小钱感谢ElementaryOS开发者。&lt;/p&gt;
&lt;p&gt;&lt;img alt="elementaryos" src="/usr/uploads/2025/11/1763810384.2956674.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2016年，Elementary OS 系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Elementary OS还有几个其它的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基于Ubuntu LTS 版本开发，因此，版本发布总是比Ubuntu晚了1年多。&lt;/li&gt;
&lt;li&gt;在大版本迭代中，没有持续的激动人心和惊艳的新功能。&lt;/li&gt;
&lt;li&gt;新冠疫情期间，开发团队内部出现分歧，导致开发人员流失。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;作为工作使用的电脑，我还是力求稳定，2年一次系统升级对我来说可接受而且必要的。但Elementary OS看起来有些难以为继，只能寻找下一个平台了。&lt;/p&gt;
&lt;h3 id="pop_os"&gt;Pop!_OS&lt;/h3&gt;
&lt;p&gt;当Elementary OS可能不再可靠之后，在 &lt;a href="https://distrowatch.com"&gt;distrowatch.com&lt;/a&gt; 选了选，看中了排名靠前的 &lt;code&gt;Pop!_OS&lt;/code&gt; 发行版。 也是基于Ubuntu的，提供了定制的GNOME桌面。&lt;/p&gt;
&lt;p&gt;回归了Gnome桌面后发现，Gnome比许多年前已经成熟了很多，重要的是有许多优秀的插件可用。回顾这么多年，不管换什么发行版，都是基于Ubuntu的衍生版，都是最初的Gnome使用习惯。&lt;/p&gt;
&lt;p&gt;&lt;img alt="popos" src="/usr/uploads/2025/11/1763810469.2505155.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2023 Pop!_OS，用回了Gnome桌面。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在Pop!_OS的使用体验还不错。重要的是，从Pop!_OS中看到了最新版Gnome桌面环境的可用性已经有了相当大的提升。&lt;/p&gt;
&lt;h3 id="debian"&gt;归宿：Debian&lt;/h3&gt;
&lt;p&gt;2023年，Debian 12 （代号 bookworm）发布，后续就直接换上了Debian 12，使用体验和其衍生版本几乎没有区别。简单的插件配置就能轻松定制一个漂亮、高效的桌面环境。使用过程稳定又可靠。&lt;/p&gt;
&lt;p&gt;如果一切都稳定可靠，未来很长一段时间大概都会继续使用Debian 。&lt;/p&gt;
&lt;h3 id="thinkpad-x1-carbon"&gt;Thinkpad X1 Carbon&lt;/h3&gt;
&lt;p&gt;2022年以前，一直使用公司提供的ThinkPad L470，重量2kg，日常携带出门非常锻炼身体。在切换到Pop!_OS系统时，最终下定决心入手一台二手的thinkpad x1 carbon gen6（2018款）。主要看重这款设备的轻量便携：重量大约1kg，外观优雅，十分方便带出门。&lt;/p&gt;
&lt;p&gt;来到2025年，Thinkpad x1 carbon gen6的16G内存有些不够用了，浏览器、企业微信、钉钉、飞书每一个都是内存占用大户，开启zram内存压缩也不够，经常要手动杀进程释放内存，否则就可能卡死桌面。于是给自己物色了二手的Thinkpad X1 Carbon Gen10（2022款），有32G内存。本想等着Debian 13发布之后，同步换新设备装新系统。但还没等到Debian 13发布，内存不足的问题就让人受不了。于是在5月底先买了电脑。初到手时，就感觉这个2022年的thinkpad在产品力方面相较2018款有不少倒退：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;屏幕面太厚太重，单手掀开盖子有点费劲。设备整体比2018年款更厚了，2018款从各个角度看都更加纤细优美，拿着更舒服。&lt;/li&gt;
&lt;li&gt;笔记本A面的设计更丑了，甚至logo都没对齐。后续用贴纸盖住。&lt;/li&gt;
&lt;li&gt;底部垫高从2个圆点变成一根长条，在桌子上拖动电脑会发出尖锐刺耳的噪音。&lt;/li&gt;
&lt;li&gt;键盘左手区域发烫，右手区域冰凉。&lt;/li&gt;
&lt;li&gt;2018年款的显示器有2k，新款里砍掉了这个配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过2022款也有好的地方，新款的笔记本的长宽比例更舒服。一段时间之后，各方面也就都适应了。&lt;/p&gt;
&lt;p&gt;8月9号 Debian终于发布了第13个版本，代号Trixie。但直到国庆节才有时间安装新系统（相较于升级，我更喜欢干净的重装），安装之后，将扩展和配置挨个都导入回来，系统上手也非常快。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在数据迁移过程发生了一个小插曲：迁移到一半时，不小心删除了新电脑的家目录，大量已经迁移的配置和数据丢失了，有些数据找不回来了。之后赶紧把家目录做成LVM，并开启LVM快照，还是得定期做快照才行。重新配置和恢复又浪费了大约一天的假期时间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="debian13" src="/usr/uploads/2025/11/1763810946.149716.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2025 Debian 13&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="gnome"&gt;Gnome桌面环境&lt;/h3&gt;
&lt;p&gt;我依然偏爱Gnome桌面环境，简洁高效。但Gnome桌面的问题依旧不小，比如Gnome的API经常变动导致有些扩展在升级后失效。许多比较基础的桌面功能没有内置，需要安装扩展解决，一般需要安装好些扩展桌面才能好用起来。&lt;/p&gt;
&lt;p&gt;新版Gnome中逐渐抛弃了panel上的后台应用icon支持，这点有些难以接受，目前Gnome 48版本还能通过扩展实现，但是部分icon的透明背景变成了黑色，十分丑陋，不知道未来的版本会如何。&lt;/p&gt;
&lt;p&gt;Gnome开发者团队以其对简洁和统一的极致追求而闻名，有时甚至显得有些固执。在我看来，系统不应替用户决定‘什么是最好的’，而应将选择权交还给用户和应用开发者，提供更多的灵活性。”&lt;/p&gt;
&lt;h3 id="_1"&gt;好的变化&lt;/h3&gt;
&lt;p&gt;以我用Linux系统作为主力机的十年来说，也有许多好的趋势：比如 systemd 统一了init和服务管理，没有引入更多分裂，简直是个奇迹；Flatpak和Appimage简化了应用安装和运行；Gnome的打磨越来越细致了；Ubuntu放弃了自己的Unity桌面，回归Gnome的怀抱；许多国民级应用官方支持Linux了；硬件的兼容性越来越好；系统的稳定性也在增强。&lt;/p&gt;
&lt;h3 id="_2"&gt;令人担忧的方面&lt;/h3&gt;
&lt;p&gt;我个人对于X11和wayland的纷争不感兴趣，但是每次尝试切换到wayland之后，有些快捷键需要手动设置，投屏会出现黑屏。这些问题很让人困扰。而后续的Gnome默认不再支持X11。&lt;/p&gt;
&lt;p&gt;软件包分裂的问题依旧存在，Ubuntu对snap软件包的坚持也是我放弃Ubuntu的一大原因。&lt;/p&gt;
&lt;p&gt;总体而言，还是好的方面更多一些。&lt;/p&gt;
&lt;h3 id="linux"&gt;普通人是否能用Linux桌面系统？&lt;/h3&gt;
&lt;p&gt;很难直接回答Linux是否适合普通人使用，但是我建议是值得一试。Linux桌面系统并不是个怪物，也并不比Windows和MacOS更复杂。国内的Linux用户也不少，且不仅局限于IT从业者。遇到问题时上网也能查到很多资料。&lt;/p&gt;
&lt;p&gt;当然，使用Linux时会有许多看起来复杂深奥的东西，但那些大部分是面向开发者的。因为Linux开放，所以这些让人如坠云雾的术语和底层知识会暴露给用户，但是这么多年来，我也没有了解多少细节，但依旧能用好这个系统。&lt;/p&gt;
&lt;p&gt;对于普通用户来说，Linux桌面有软件中心、有扩展平台，几乎可以满足大部分的日常需求，甚至比Windows还简单。重要的是，Linux上没有流氓软件和定时炸弹般的弹窗，也没有盗版的烦恼。&lt;/p&gt;
&lt;p&gt;对于国内用户来说，微信、腾讯视频会议、WPS、搜狗输入法、网易云音乐这些日常应用已经有Linux版本，而且有些比Windows上的体验更好。如果有兴趣，不如迈出那一步给自己一个机会。&lt;/p&gt;
&lt;h3 id="_3"&gt;总结&lt;/h3&gt;
&lt;p&gt;一个月以来，Debian 13的使用体验令人满意，这套软硬件组合预计还能再战数年。未来十年，Linux桌面仍将是我数字生活的重要组成部分。&lt;/p&gt;
&lt;p&gt;我认识的许多研发工程师，甚至不知道Linux拥有成熟可用的桌面环境，仍以为它只是一个“黑乎乎的命令行”。看来，Linux桌面的普及之路，依旧任重而道远。&lt;/p&gt;
&lt;p&gt;回首这十多年的历史，Linux和它的桌面系统一直在默默成长，变得越来越好。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/linuxes/my-linux.html</guid>
      <pubDate>Sat, 22 Nov 2025 19:36:07 +0800</pubDate>
    </item>
    <item>
      <title>SQLAlchemy中的联表查询</title>
      <link>http://xnow.me/programs/relationships-in-sqlalchemy.html</link>
      <description>&lt;p&gt;&lt;img alt="heavy-cloud-in-bluesky" src="/usr/uploads/2025/08/1756635273.4136176.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;使用关系型数据库时，联表查询是一项常见的数据库操作。外键可以简化联表查询操作，确保数据的强约束和完整性。但对数据库管理而言，外键使得数据库的设计和维护变得复杂，降低灵活性。因此，许多公司禁止在业务中使用外键，而是在业务代码层保持数据的一致性。&lt;/p&gt;
&lt;p&gt;Python中最流行的 SQLAlchemy 这个ORM库就能通过外键和自带的 relationship 函数实现跨表数据模型间的关系绑定。提供了基于外键，和基于join的2种方法。本文将从简到繁的一一介绍。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="heavy-cloud-in-bluesky" src="/usr/uploads/2025/08/1756635273.4136176.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;使用关系型数据库时，联表查询是一项常见的数据库操作。外键可以简化联表查询操作，确保数据的强约束和完整性。但对数据库管理而言，外键使得数据库的设计和维护变得复杂，降低灵活性。因此，许多公司禁止在业务中使用外键，而是在业务代码层保持数据的一致性。&lt;/p&gt;
&lt;p&gt;Python中最流行的 SQLAlchemy 这个ORM库就能通过外键和自带的 relationship 函数实现跨表数据模型间的关系绑定。提供了基于外键，和基于join的2种方法。本文将从简到繁的一一介绍。&lt;/p&gt;
&lt;!--more--&gt;

&lt;blockquote&gt;
&lt;p&gt;ORM 框架用于在关系型数据库和面向对象编程语言之间进行数据转换，允许开发者使用面向对象的方式来操作数据库，而不需要直接编写SQL语句，大大简化数据操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本来以常见的文章（post）数据库为例，比如数据库中有文章表（post）、文章分类表（category）和标签表（tag）。一般三者分属于3张表，但我们希望通过如下的方法直接访问文章的所属的分类和标签信息，就像文章表带了标签和分类字段一样。&lt;/p&gt;
&lt;p&gt;期望的使用方法：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;   &lt;span class="c1"&gt;# 获取文章所属的分类&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;      &lt;span class="c1"&gt;# 获取文章的标签列表&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;本文将展示三种不同的方法实现以上需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用基于类property的笨办法，底层用ORM的查询语句&lt;/li&gt;
&lt;li&gt;使用外键，会增加数据库管理成本&lt;/li&gt;
&lt;li&gt;使用join，既方便又直观&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了上面的单向查询，本文还有双向查询，即通过分类或者标签表的字段，反查与之相关联的文章列表。&lt;/p&gt;
&lt;p&gt;以下代码的测试环境如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python：3.12.7&lt;/li&gt;
&lt;li&gt;SQLAlchemy：2.0.40&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="property"&gt;笨办法：基于property装饰器&lt;/h3&gt;
&lt;p&gt;为了避免使用外键，在很长一段时间里都在写下面这类代码实现联表查询。给数据模型定义一些property的方法，用于查询文章所属的分类。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Post表和Category表通过Post表中的category_id字段关联起来。&lt;code&gt;@property&lt;/code&gt; 装饰器使得可以用 &lt;code&gt;Post.category&lt;/code&gt;查询文章的所属分类。&lt;/p&gt;
&lt;p&gt;为此，只要引用其它表字段，都需要定义对应的property函数。接下来，再看看SQLAlchemy提供了什么有趣的方法。&lt;/p&gt;
&lt;h3 id="_1"&gt;基于外键的一对多双向查询方案&lt;/h3&gt;
&lt;p&gt;先从最简单的外键方案开始。设计一个简单场景，有文章表(posts)和分类表(categories)，分类和文章属于一对多关系：一篇文章仅属于一个分类，一个分类下有多篇文章。&lt;/p&gt;
&lt;p&gt;业务需求是双向查询：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在文章模型中，可以通过category属性获得文章所属的分类&lt;/li&gt;
&lt;li&gt;在分类模型中，可以通过 posts 属性获取分类下的所有文章&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定义Category和Post两个模型类，将Post的category_id 列设置为外键。定义如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.orm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DeclarativeBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeclarativeBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;categories&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;Category(id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, name=&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;)&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;categories.id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Category 中定义了 posts 这个属性用于获取分类下的所有文章列表。posts 成员变量是用 &lt;code&gt;relationship&lt;/code&gt; 创建的到 Post类的引用。由于 Post 对象此时未定义，所以写成字符串格式 "Post" 。&lt;/li&gt;
&lt;li&gt;Post 中定义了 category_id 这个外键，关联到 categories.id 字段&lt;/li&gt;
&lt;li&gt;Post 中也定义了category属性用于获取文章的分类，并使用 back_populates 做反向引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：relationship 函数反向引用的字段值 category 和 posts 是本对象（Category和Post）在目标对象（Post和Category）中属性名（category和posts）。&lt;/p&gt;
&lt;p&gt;使用上面定义的模型，创建数据库、写入测试数据进行测试。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.orm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sessionmaker&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sqlite:///blog.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 添加2个分类&lt;/span&gt;
&lt;span class="n"&gt;tech&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Technology&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;life&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Life&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;life&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 创建3篇文章&lt;/span&gt;
&lt;span class="n"&gt;post1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Python ORM&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SQLAlchemy tutorial&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Daily Life&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My weekend&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;life&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Advanced Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Decorators explained&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 通过文章获取分类&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 通过分类获取所有文章&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;执行结果，符合预期&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;sqlalchemy
$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;main1.py
&amp;lt;Category&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Technology&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&amp;gt;
----------
Python&lt;span class="w"&gt; &lt;/span&gt;ORM
Advanced&lt;span class="w"&gt; &lt;/span&gt;Python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看sqlite中的表结构&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sqlite3&lt;span class="w"&gt;  &lt;/span&gt;blog.db
sqlite&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.schema
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;categories&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;posts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;title&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;category_id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;FOREIGN&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="o"&gt;(&lt;/span&gt;category_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;REFERENCES&lt;span class="w"&gt; &lt;/span&gt;categories&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;看到 sqlite的posts 表中，category_id被定义成了外键，指向 categories 表的id字段。&lt;/p&gt;
&lt;p&gt;上面整个模型的定义是比较简洁的，SQLAlchemy会利用外键，自动将两张表、两个数据模型关联起来，没有许多额外的设置。&lt;/p&gt;
&lt;h3 id="orm"&gt;基于ORM的双向关联查询&lt;/h3&gt;
&lt;p&gt;如果禁止了外键，开发者依旧希望使用简单方法获取数据实体对象间的关联信息。这就是基于join的方案。既然没有外键，就必须人为指定模型之间的关联方法，声明使用哪两列进行join操作。修改模型的定义如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;categories&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Category.id == foreign(Post.category_id)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;Category(id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, name=&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;)&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post.category_id == Category.id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;foreign_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sqlite:///blog2.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;在Category中的posts属性定义使用了 primaryjoin参数，显式指定两个模型的join规则。&lt;/li&gt;
&lt;li&gt;Post.category_id 不再是外键&lt;/li&gt;
&lt;li&gt;在 Category.posts 和 Post.category 两个字段中，都指定了join关系和反向引用。&lt;/li&gt;
&lt;li&gt;在两个relationship函数中，前者使用了 &lt;code&gt;"Category.id == foreign(Post.category_id)"&lt;/code&gt; 字符串, 后者使用了独立参数 &lt;code&gt;foreign_keys=[category_id]&lt;/code&gt; 。 二者的效果是一样的，这里故意用两种方法只是为了展示多种用法。foreign_keys参数的值也可以是字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其它代码不用变，执行效果和使用外键一样，符合预期。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;main2.py&lt;span class="w"&gt; &lt;/span&gt;
&amp;lt;Category&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Technology&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&amp;gt;
----------
Python&lt;span class="w"&gt; &lt;/span&gt;ORM
Advanced&lt;span class="w"&gt; &lt;/span&gt;Python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查表结构，注意 posts.category_id 不再是外键了&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sqlite3&lt;span class="w"&gt; &lt;/span&gt;blog2.db&lt;span class="w"&gt; &lt;/span&gt;
sqlite&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.schema&lt;span class="w"&gt; &lt;/span&gt;
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;categories&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;posts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;title&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;category_id&lt;span class="w"&gt; &lt;/span&gt;INTEGER,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;数据库里没有外键，而是在ORM层实现了类似外键的效果，底层实际使用了join的方法将两个表关联起来。由于没有外键，所以更需要注意在业务代码中确保数据的一致性。&lt;/p&gt;
&lt;h3 id="orm_1"&gt;基于ORM的单向关系查询&lt;/h3&gt;
&lt;p&gt;之前是双向的查询，这里顺带也看看单向查询的例子，在这个例子中，单向查询的含义是，只能查询文章的分类，不能通过分类获取分类下的文章列表。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;categories&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;Category(id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, name=&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;)&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foreign(Post.category_id) == Category.id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;                                        
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;与之前代码的差别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Category中，删除了 posts 属性&lt;/li&gt;
&lt;li&gt;Post中，relationship 函数里，删除了 back_populates 参数，因为不需要反向映射了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行代码可以发现，&lt;code&gt;post.category&lt;/code&gt;  依旧能获取到文章下的分类信息，但显然已经无法执行&lt;code&gt;Category.post&lt;/code&gt; 了。&lt;/p&gt;
&lt;h3 id="orm_2"&gt;基于ORM的多对多关联表查询&lt;/h3&gt;
&lt;p&gt;除了上面分类和文章的一对多场景，常见的数据关系还有多对多关系，这里以文章和标签为例：一篇文章有多个标签，一个标签关联到多篇文章。这种场景一般会引入一张 relationships 的表，记录文章和标签的对应关系。模型定义如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;  &lt;span class="c1"&gt;# 新增relationships表&lt;/span&gt;

    &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;primaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Tag.id == Relationship.tag_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;secondaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Relationship.post_id == Post.id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;Tag(id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, name=&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;)&amp;gt;&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;relationships&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;primaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post.id == Relationship.post_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;secondaryjoin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Tag.id == Relationship.tag_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;back_populates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;posts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;uselist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这里 relationship 函数的参数多了一些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义了 relationships 表，表中post_id和tag_id两个字段分别是文章id和标签的id。&lt;/li&gt;
&lt;li&gt;在 Tag 类的 posts 属性中，relationship 函数新引入了&lt;code&gt;secondary&lt;/code&gt; 和 &lt;code&gt;secondaryjoin&lt;/code&gt; 参数，用于把 relationships 表加入联表查询，实现3张表的联动。注意 secondary 的值必须是字符串格式的表名，而不能是映射的类名。&lt;/li&gt;
&lt;li&gt;在 primaryjoin 和 secondaryjoin中共执行了两次join操作，将3张表关联起来。&lt;/li&gt;
&lt;li&gt;Post类中，relationship的用法和Tag 中一样。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建表和数据，验证效果。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sqlite:///blog4.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 添加3个分类&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;linux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;devops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;devops&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;devops&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 创建3篇文章&lt;/span&gt;
&lt;span class="n"&gt;post1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Python ORM&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SQLAlchemy tutorial&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Build Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Build Python on linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Drone CI&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Deploy app with Drone on linux&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 将文章和标签进行关联&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;devops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 获取文章的标签&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 通过标签获取所有文章&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;执行结果，成功获得文章包含的tag，以及打了对应tag的文章：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;main4.py
&lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Tag&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Python&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&amp;gt;,&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;Tag&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Linux&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;
----------
Build&lt;span class="w"&gt; &lt;/span&gt;Python
Drone&lt;span class="w"&gt; &lt;/span&gt;CI
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看表结构，也没有外键。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.schema&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sqlite3&lt;span class="w"&gt; &lt;/span&gt;blog4.db
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;relationships&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;tag_id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;post_id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;tag_id,&lt;span class="w"&gt; &lt;/span&gt;post_id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE&lt;span class="w"&gt; &lt;/span&gt;TABLE&lt;span class="w"&gt; &lt;/span&gt;posts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;INTEGER&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;title&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NOT&lt;span class="w"&gt; &lt;/span&gt;NULL,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;PRIMARY&lt;span class="w"&gt; &lt;/span&gt;KEY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;数据库里的 relationships 表使用了联合主键。&lt;/p&gt;
&lt;h3 id="_2"&gt;总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用外键可以简化SQLAlchemy模型类的定义，方便代码中进行数据查询。&lt;/li&gt;
&lt;li&gt;但外键也可能带来性能损失、增加运维管理复杂度，因此许多公司禁用了数据库外键。&lt;/li&gt;
&lt;li&gt;通过SQLAlchemy 的relationship函数提供的方法，可以在ORM层定义模型关联方法。实现一对多和多对多的联表查询。&lt;/li&gt;
&lt;li&gt;在使用SQLALchemy 提供的模型关联方法时，要注意在代码层确保数据的一致性。&lt;/li&gt;
&lt;/ul&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/relationships-in-sqlalchemy.html</guid>
      <pubDate>Sun, 31 Aug 2025 18:26:52 +0800</pubDate>
    </item>
    <item>
      <title>EFK日志体系快速入门</title>
      <link>http://xnow.me/ops/efk-tutorial.html</link>
      <description>&lt;p&gt;&lt;img alt="Distant Storm" src="/usr/uploads/2025/07/1753875920.8339665.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;7月，远方的风暴。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Elasticsearch、Filebeat、Kibana 合称EFK，常用于构建企业级的日志系统。Filebeat 在其中负责数据的采集：读取日志文件，将其发往Elasticsearch；Elasticsearch是数据库，处理和存储Filebeat发过来的数据；Kibana是UI，用于查询Elasticsearch中的数据。三者协作，构成了完整的日志平台。此外，Elasticsearch具有强大的全文搜索能力，也常在业务上用作搜索引擎。&lt;/p&gt;
&lt;p&gt;本文将使用docker compose编排Nginx、Filebeat、Elasticsearch和Kibana，实现将Nginx的access log分字段解析后存储到Elasticsearch，并在Kibana上展示和查询。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="Distant Storm" src="/usr/uploads/2025/07/1753875920.8339665.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;7月，远方的风暴。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Elasticsearch、Filebeat、Kibana 合称EFK，常用于构建企业级的日志系统。Filebeat 在其中负责数据的采集：读取日志文件，将其发往Elasticsearch；Elasticsearch是数据库，处理和存储Filebeat发过来的数据；Kibana是UI，用于查询Elasticsearch中的数据。三者协作，构成了完整的日志平台。此外，Elasticsearch具有强大的全文搜索能力，也常在业务上用作搜索引擎。&lt;/p&gt;
&lt;p&gt;本文将使用docker compose编排Nginx、Filebeat、Elasticsearch和Kibana，实现将Nginx的access log分字段解析后存储到Elasticsearch，并在Kibana上展示和查询。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;演示环境：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host：Debian 12&lt;/li&gt;
&lt;li&gt;Docker版本：Docker 28.3.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EFK各软件的版本在compose.yaml 中均有指定。不同版本Elasticsearch、Filebeat、Kibana的配置与界面很可能不一样，尽量使用新版本，如果和文章中的版本有出入导致无法成功配置，请看看官方文档。&lt;/p&gt;
&lt;p&gt;创建工作目录，各配置文件都放到该目录下。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/tmp/efk
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/efk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="nginx"&gt;Nginx配置文件&lt;/h3&gt;
&lt;p&gt;创建Nginx的目录，存放Nginx的配置文件和日志&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;nginx/logs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;编辑Nginx的配置文件 nginx/nginx.conf，创建如下的精简化配置&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;worker_processes&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;error_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;/var/log/nginx/error.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;notice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pid&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;/run/nginx.pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;worker_connections&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;include&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s"&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;default_type&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;log_format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;[&lt;/span&gt;&lt;span class="nv"&gt;$time_local]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$body_bytes_sent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$http_user_agent&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$http_referer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$http_x_forwarded_for&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$request_time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;/var/log/nginx/access.log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;sendfile&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;keepalive_timeout&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kn"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kn"&gt;root&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kn"&gt;index&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在文件中添加了server段配置。此外，修改了 Nginx的access log的格式，使用竖线分割日志各字段，可读性更高，也更方便使用工具分析。后续在Elasticsearch中，将使用pipeline对Nginx日志进行解析。&lt;/p&gt;
&lt;h3 id="filebeat"&gt;Filebeat配置&lt;/h3&gt;
&lt;p&gt;和Filebeat具备相同采集能力的还有Logstash、Fluentd等，选择Filebeat的原因是因为它用Golang编写，足够轻量，占用的资源少，适合在每个节点上作为守护进程部署。&lt;/p&gt;
&lt;p&gt;创建Filebeat的配置文件目录&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;filebeat/data
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;:1000&lt;span class="w"&gt; &lt;/span&gt;filebeat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建fileabeat的配置文件，filebeat/filebeat.yml，内容如下&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;filebeat.inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;filestream&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx_access_log_id&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;prospector.scanner.fingerprint.length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/tmp/logs/access.log&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;log_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx_access_log&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nginx_access_log_pipeline&amp;quot;&lt;/span&gt;


&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;filestream&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx_error_log_id&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;prospector.scanner.fingerprint.length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;64&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/tmp/logs/error.log&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;log_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx_error_log&lt;/span&gt;

&lt;span class="nt"&gt;output.elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;elasticsearch:9200&amp;#39;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;flush_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1s&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;nginx_access_log-%{+yyyy_MM_dd}&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;when.equals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;fields.log_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nginx_access_log&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;nginx_error_log-%{+yyyy_MM_dd}&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;when.equals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;fields.log_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nginx_error_log&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;文件中定义了2个input，分别读取Nginx的access.log 和 error.log日志文件，并发往Elasticsearch中对应的index。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elasticsearch的index的名字中使用了变量&lt;code&gt;%{+yyyy_MM_dd}&lt;/code&gt;，用于区分index的日期，便于后续按日期删除久远的index。&lt;/li&gt;
&lt;li&gt;对access log的index配置了pipeline。这个pipeline是用于解析Nginx的access log的。之后会手动在Elasticsearch中创建这个pipeline。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Filebeat的配置建议参考：https://www.elastic.co/docs/reference/beats/filebeat/filebeat-reference-yml&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Filebeat除了可以将日志输出到Elasticsearch外，支持的output插件的还有Kafka和Logstash等等。使用Kafka（&lt;code&gt;Filebeat -&amp;gt; Kafka -&amp;gt; Elasticsearch&lt;/code&gt;）的好处是可以做到流量削峰，应对瞬时大流量；使用Logstash（&lt;code&gt;Filebeat -&amp;gt; Logstash -&amp;gt; Elasticsearch&lt;/code&gt;）的好处是可以利用Logstash丰富的日志解析处理能力；当然，也能将二者进一步结合，形成&lt;code&gt;Filebeat -&amp;gt; Kafka -&amp;gt; Logstash -&amp;gt; Elasticsearch&lt;/code&gt; 的处理链路。不论经历了多少轮中间件的处理，日志的最终归宿一般还是Elasticsearch。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="docker-compose"&gt;docker compose 部署&lt;/h3&gt;
&lt;p&gt;创建Elasticsearch的数据目录&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;es_data
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;:1000&lt;span class="w"&gt; &lt;/span&gt;es_data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建compose.yaml文件，编排所有的容器，内容如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;nginx:1.29.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./nginx/logs/:/var/log/nginx/&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# 日志输出到宿主机目录&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./nginx/nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80:80&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;filebeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elastic/filebeat:9.1.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;filebeat&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;always&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./filebeat/data:/usr/share/filebeat/data&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./nginx/logs:/tmp/logs:ro&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 挂载宿主机上的日志目录到容器内&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elasticsearch:9.0.4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;elasticsearch&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;always&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster.name=elasticsearch&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ES_JAVA_OPTS=-Xms1g&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-Xmx1g&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;discovery.type=single-node&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xpack.security.enabled=false&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;xpack.security.enrollment.enabled=false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;9200:9200&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;9300:9300&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./es_data:/usr/share/elasticsearch/data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5s&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;60&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;curl --fail -s -o /dev/null http://127.0.0.1:9200/&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;kibana&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kibana:9.0.4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kibana&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;always&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ELASTICSEARCH_URL=http://elasticsearch:9200&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;5601:5601&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;elasticsearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;service_healthy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx： 把nginx/nginx.conf 映射到容器内，把Nginx容器里的日志目录映射到宿主机上。&lt;/li&gt;
&lt;li&gt;filebeat：把Nginx的日志以只读方式映射到Filebeat容器内，方便Filebeat读取和采集。&lt;/li&gt;
&lt;li&gt;elasticsearch：将Elasticsearch的数据保存目录映射到外部做数据持久化。&lt;/li&gt;
&lt;li&gt;kibana：配置Elasticsearch的地址配置到环境变量中，启动的时候会依赖Elasticsearch。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文这里使用了单节点的Elasticsearch。如果想了解在Docker中对Elasticsearch做集群化部署的方案，请查看官方文档：https://www.elastic.co/docs/deploy-manage/deploy/self-managed/install-elasticsearch-docker-compose&lt;/p&gt;
&lt;p&gt;安全声明：本文简化了部署的配置，关闭了Elasticsearch的认证等功能，在生产中部署时，还是需要启用这些安全选项。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;启动compose&lt;/strong&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查Elasticsearch的状态&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_cat/health?v
epoch&lt;span class="w"&gt;      &lt;/span&gt;timestamp&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt;       &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;node.total&lt;span class="w"&gt; &lt;/span&gt;node.data&lt;span class="w"&gt; &lt;/span&gt;shards&lt;span class="w"&gt; &lt;/span&gt;pri&lt;span class="w"&gt; &lt;/span&gt;relo&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;unassign&lt;span class="w"&gt; &lt;/span&gt;unassign.pri&lt;span class="w"&gt; &lt;/span&gt;pending_tasks&lt;span class="w"&gt; &lt;/span&gt;max_task_wait_time&lt;span class="w"&gt; &lt;/span&gt;active_shards_percent
&lt;span class="m"&gt;1753751269&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;:07:49&lt;span class="w"&gt;  &lt;/span&gt;elasticsearch&lt;span class="w"&gt; &lt;/span&gt;yellow&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;-&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="m"&gt;88&lt;/span&gt;.1%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;正常的Elasticsearch集群状态应该是green，由于这里是单节点Elasticsearch，所以状态是yellow，但不影响后面的操作。&lt;/p&gt;
&lt;h3 id="elasticsearchpipeline"&gt;Elasticsearch配置pipeline&lt;/h3&gt;
&lt;p&gt;Elasticsearch中pipeline可以对消息执行解析、拆分、转化、增删等操作。详细可以查看文档： https://www.elastic.co/docs/reference/enrich-processor/&lt;/p&gt;
&lt;p&gt;创建如下的json文件，命名为 nginx_access_log_pipeline.json，内容是用于解析Nginx的access log的pipepine。其中的grok处理器提供了从字符串字段中提取并结构化字段的模式，为各个字段指定名字，设置类型。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nginx access log pipeline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;processors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;grok&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;patterns&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\\[%{HTTPDATE:timestamp}\\] \\| %{DATA:domain} \\| %{IP:client_ip} \\| \&amp;quot;%{DATA:remote_user}\&amp;quot; \\| %{WORD:method} %{DATA:request} %{DATA:http_version} \\| %{NUMBER:status_code:int} \\| %{NUMBER:response_size:int} \\| \&amp;quot;%{DATA:user_agent}\&amp;quot; \\| \&amp;quot;%{DATA:referer}\&amp;quot; \\| \&amp;quot;%{DATA:x_forwarded_for}\&amp;quot; \\| %{NUMBER:response_time:float}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ignore_missing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;target_field&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;formats&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dd/MMM/yyyy:HH:mm:ss Z&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ecs.version&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过Elasticsearch接口创建pipeline&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;PUT&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content-type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;http://127.0.0.1:9200/_ingest/pipeline/nginx_access_log_pipeline&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;@./nginx_access_log_pipeline.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查创建好的pipeline&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 查看所有pipline&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_ingest/pipeline&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.

&lt;span class="c1"&gt;# 或者，查看具体的pipeline&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_ingest/pipeline/nginx_access_log_pipeline?pretty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;pipeline相关api的文档：https://www.elastic.co/guide/en/elasticsearch/reference/master/get-pipeline-api.html&lt;/p&gt;
&lt;h3 id="_1"&gt;测试日志采集&lt;/h3&gt;
&lt;p&gt;启动docker compose后，访问Nginx会产生日志，并被Filebeat采集和发往Elasticsearch中。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1/&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# status_code 200&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1/x&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# status_code 404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看Elasticsearch中的日志数量&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_cat/indices?v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/nginx/ || NR ==1&amp;#39;&lt;/span&gt;
health&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;index&lt;span class="w"&gt;                          &lt;/span&gt;uuid&lt;span class="w"&gt;                   &lt;/span&gt;pri&lt;span class="w"&gt; &lt;/span&gt;rep&lt;span class="w"&gt; &lt;/span&gt;docs.count&lt;span class="w"&gt; &lt;/span&gt;docs.deleted&lt;span class="w"&gt; &lt;/span&gt;store.size&lt;span class="w"&gt; &lt;/span&gt;pri.store.size&lt;span class="w"&gt; &lt;/span&gt;dataset.size
yellow&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt;   &lt;/span&gt;nginx_access_log-2025_07_30&lt;span class="w"&gt;    &lt;/span&gt;L4T0gXEJQjiOIIvOX3oBhw&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;227b&lt;span class="w"&gt;           &lt;/span&gt;227b&lt;span class="w"&gt;         &lt;/span&gt;227b
yellow&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt;   &lt;/span&gt;nginx_error_log-2025_07_30&lt;span class="w"&gt;     &lt;/span&gt;88e58AUhQ3SW2_zRNWPj7w&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;.7kb&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;.7kb&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;.7kb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这里可以看到Nginx的access log和error log的条目数量和占用的空间大小。&lt;/p&gt;
&lt;h3 id="kibana"&gt;配置Kibana查询&lt;/h3&gt;
&lt;p&gt;在浏览器中打开Kibana的地址: http://127.0.0.1:5601 。 通过如下的方法，创建Kibana的数据视图，并在Kibana中执行各式各样的查询方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建Kibana的Data View&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 ）打开Kibana的左侧边栏，点击&lt;code&gt;Management&lt;/code&gt;，然后继续在左侧边栏的&lt;code&gt;Kibana&lt;/code&gt;下点击&lt;code&gt;Data Views&lt;/code&gt; 。或者直接打开 http://127.0.0.1:5601/app/management/kibana/dataViews 。&lt;/li&gt;
&lt;li&gt;2）在新页面中点击&lt;code&gt;Create data view&lt;/code&gt;这个按钮。在弹出的表单中填写数据视图。&lt;ul&gt;
&lt;li&gt;Name：nginx_access_log&lt;/li&gt;
&lt;li&gt;Index pattern：nginx_access_log-*&lt;/li&gt;
&lt;li&gt;填写好之后，点击 &lt;code&gt;Save data view to Kibana&lt;/code&gt; 按钮提交。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3）用和上面同样的方法为Nginx的error log创建 &lt;code&gt;nginx_error_log&lt;/code&gt;的Data view。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;在Kibana中查询日志&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1）打开左侧栏，在 &lt;code&gt;Analytics&lt;/code&gt; 下点击 &lt;code&gt;Discover&lt;/code&gt; ，或者直接打开链接 http://127.0.0.1:5601/app/discover#/ 。&lt;/li&gt;
&lt;li&gt;2）在页面左上角的 &lt;code&gt;Data view&lt;/code&gt;中选中刚刚创建的 nginx_access_log，就可以看到刚刚生成的access log了，有2条访问记录。&lt;ul&gt;
&lt;li&gt;在左侧可以看到日志的拆分后的所有字段&lt;/li&gt;
&lt;li&gt;在搜索框中输入 &lt;code&gt;status_code: 404&lt;/code&gt; 查看http返回码为 404的日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3）Kibana上切换Data view到&lt;code&gt;nginx_error_log&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;查看界面左侧的日志字段，由于没有用pipeline做字段解析，大部分都是内置字段&lt;/li&gt;
&lt;li&gt;在搜索框中输入 &lt;code&gt;"no such file"&lt;/code&gt;，使用模糊搜索的方式查询日志，可以查到对应的错误日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="elasticsearchapi"&gt;Elasticsearch的常用API&lt;/h3&gt;
&lt;p&gt;Elasticsearch 的API接口操作方法见链接，https://www.elastic.co/docs/api/doc/elasticsearch/v9/&lt;/p&gt;
&lt;p&gt;检查集群的基本信息，包括各组组件版本，兼容性。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看集群的健康状态&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_cluster/health?pretty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看Elasticsearch集群的节点信息&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_cat/nodes?v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;index的操作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;创建index&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;PUT&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content-type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/test-00001&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;settings&amp;quot;: {&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;number_of_shards&amp;quot;: 1,&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;number_of_replicas&amp;quot;: 1&lt;/span&gt;
&lt;span class="s1"&gt;  }&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看index的设置&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/test-00001/_settings?pretty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看所有index&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_cat/indices
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看具体index的信息，包括分片、副本等详细信息&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/test-00001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;向index批量写入数据，如果指定的index不存在，会自动创建&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/_bulk?pretty&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-d&lt;span class="s1"&gt;&amp;#39;{ &amp;quot;index&amp;quot; : { &amp;quot;_index&amp;quot; : &amp;quot;test-00001&amp;quot; } }&lt;/span&gt;
&lt;span class="s1"&gt;    {&amp;quot;name&amp;quot;: &amp;quot;kong&amp;quot;, &amp;quot;age&amp;quot;: &amp;quot;bar&amp;quot; }&lt;/span&gt;
&lt;span class="s1"&gt;    { &amp;quot;index&amp;quot; : { &amp;quot;_index&amp;quot; : &amp;quot;test-00001&amp;quot; } }&lt;/span&gt;
&lt;span class="s1"&gt;    {&amp;quot;name&amp;quot;: &amp;quot;ming&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;bar&amp;quot; }&lt;/span&gt;
&lt;span class="s1"&gt;    { &amp;quot;index&amp;quot; : { &amp;quot;_index&amp;quot; : &amp;quot;test-00001&amp;quot; } }&lt;/span&gt;
&lt;span class="s1"&gt;    {&amp;quot;name&amp;quot;: &amp;quot;fang&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;bar&amp;quot; }&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看index的mapping，mapping相当于关系数据库中的schema，用于定义索引的结构和字段类型等。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/nginx_error_log-2025_07_30/_mapping
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;删除index&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;DELETE&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/test-00001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;搜索操作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Elasticsearch的详细查询用法，参考：https://www.elastic.co/docs/reference/query-languages/querydsl ， 下面介绍常用的两种。&lt;/p&gt;
&lt;p&gt;查看access log 索引中的status_code=404的条目。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content-type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/nginx_access_log-2025_07_30/_search&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;{&amp;quot;query&amp;quot;:{&amp;quot;match&amp;quot;: {&amp;quot;status_code&amp;quot;: 404}}}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;对error log 进行模糊搜索，查找 "not found" 字符串。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content-type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:9200/nginx_error_log-2025_07_30/_search&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;{&amp;quot;query&amp;quot;:{&amp;quot;query_string&amp;quot;: {&amp;quot;query&amp;quot;:&amp;quot;No such file&amp;quot;}}}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_2"&gt;总结&lt;/h3&gt;
&lt;p&gt;本文概述了EFK日志管理系统的核心概念和组件，使用docker compose 搭建了一个简易版本的EFK系统。如果要将其用于生产环境，各个组件都还有很大的优化空间，尤其是在Elasticsearch的集群部署和安全认证方面还有很多工作要做。&lt;/p&gt;
&lt;p&gt;需要指出的是，EFK体系作为一个强大而复杂的日志管理解决方案，其功能远不止本文所涉及的入门知识，Filebeat、Elasticsearch、Kibana的配置项都十分丰富，本文仅仅浅尝了其中的百分之一。当遇到问题时，建议读读文中提到的官方文档。最后，期望你玩的愉快。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/ops/efk-tutorial.html</guid>
      <pubDate>Wed, 30 Jul 2025 19:43:08 +0800</pubDate>
    </item>
    <item>
      <title>使用ollama搭建开源AI编程助手</title>
      <link>http://xnow.me/programs/selfhosted-coding-assistant-with-ollama.html</link>
      <description>&lt;p&gt;&lt;img alt="build-shadow" src="/usr/uploads/2025/06/1750257322.7566748.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;水中投影，似真似幻&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当前，大公司大模型几乎主导了AI行业。作为个人，把所有的数据和隐私全部上交，固然能获得很好的智能体验。不过，开源生态也一如既往的提供了一些丰富的选择，对许多个人和企业来说，用开源工具私有化部署开源模型不失为一种可行的方案。&lt;/p&gt;
&lt;p&gt;这篇文章会基于开源的LLM模型（Qwen、Ollama、deepseek等）、开源的模型工具（Ollama）、开源的插件集成（VSCode+Continue插件）方案，搭建一个基础的编程编程助手。重点在于Ollama这个模型工具的用法。&lt;/p&gt;
&lt;p&gt;Ollama 是一个开源的大型语言模型（LLM）工具，专注于在本地运行和管理各种 AI 模型。支持许多流行的模型，比如 deepseek-r1、gemma3、qwen3、llama4等。Ollama大大简化了用户下载和运行模型的过程。&lt;/p&gt;
&lt;p&gt;同时，Ollama还获得了许多流行的集成工具的支持，比如&lt;a href="https://openwebui.com/"&gt;Open WebUI&lt;/a&gt;、&lt;a href="https://github.com/langgenius/dify"&gt;dify&lt;/a&gt;等等，许多开源工具都可以使用Ollama的接口。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="build-shadow" src="/usr/uploads/2025/06/1750257322.7566748.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;水中投影，似真似幻&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当前，大公司大模型几乎主导了AI行业。作为个人，把所有的数据和隐私全部上交，固然能获得很好的智能体验。不过，开源生态也一如既往的提供了一些丰富的选择，对许多个人和企业来说，用开源工具私有化部署开源模型不失为一种可行的方案。&lt;/p&gt;
&lt;p&gt;这篇文章会基于开源的LLM模型（Qwen、Ollama、deepseek等）、开源的模型工具（Ollama）、开源的插件集成（VSCode+Continue插件）方案，搭建一个基础的编程编程助手。重点在于Ollama这个模型工具的用法。&lt;/p&gt;
&lt;p&gt;Ollama 是一个开源的大型语言模型（LLM）工具，专注于在本地运行和管理各种 AI 模型。支持许多流行的模型，比如 deepseek-r1、gemma3、qwen3、llama4等。Ollama大大简化了用户下载和运行模型的过程。&lt;/p&gt;
&lt;p&gt;同时，Ollama还获得了许多流行的集成工具的支持，比如&lt;a href="https://openwebui.com/"&gt;Open WebUI&lt;/a&gt;、&lt;a href="https://github.com/langgenius/dify"&gt;dify&lt;/a&gt;等等，许多开源工具都可以使用Ollama的接口。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2 id="_1"&gt;环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;RockyLinux 9&lt;/li&gt;
&lt;li&gt;Ollama 0.9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ollama"&gt;安装Ollama&lt;/h2&gt;
&lt;p&gt;Ollama的项目地址：https://github.com/ollama/ollama
官方网站：https://ollama.com/&lt;/p&gt;
&lt;p&gt;这里使用二进制解压部署的方式。从Ollama官方下载压缩包，解压之后就可以直接使用了。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;ollama
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama
$&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://ollama.com/download/ollama-linux-amd64.tgz
$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xvf&lt;span class="w"&gt; &lt;/span&gt;ollama-linux-amd64.tgz
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$PWD&lt;/span&gt;/bin&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 将ollama命令加入PATH环境变量中&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;官方也提供了其它安装方式。参考：https://ollama.com/download&lt;/p&gt;
&lt;p&gt;安装好之后，使用&lt;code&gt;ollama --version&lt;/code&gt; 查看Ollama的版本。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;--version
ollama&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.9.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;输出版本号就代表安装好了。&lt;/p&gt;
&lt;h2 id="_2"&gt;下载和运行模型&lt;/h2&gt;
&lt;p&gt;Ollama的命令比较简单，可以通过 &lt;code&gt;ollama --help&lt;/code&gt; 查看所有的子命令。下面先介绍基础的模型管理命令。&lt;/p&gt;
&lt;h3 id="_3"&gt;模型的管理&lt;/h3&gt;
&lt;p&gt;Ollama的server端和client端都是同一个二进制文件 ollama。要运行客户端命令，先要启动Ollama的服务端。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行服务端&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama serve&lt;/code&gt; 运行Ollama服务端，测试中我们将其放置在前台运行，方便观察。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ollama 的server端运行在 11434 端口上。&lt;strong&gt;几乎所有的Ollama命令都是通过HTTP协议和Ollama服务端的交互。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下载模块&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;新开一个终端，运行&lt;code&gt;ollama pull &amp;lt;model name&amp;gt;&lt;/code&gt;的命令。这里尝试下载 &lt;code&gt;deepseek-r1:1.5b&lt;/code&gt;  和 &lt;code&gt;qwen3:4b&lt;/code&gt; 模型。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;新开窗口运行ollama命令的话，也需要执行 &lt;code&gt;export PATH=$PATH:$PWD/bin&lt;/code&gt; 添加环境变量，否则就要写全ollama可执行文件的路径。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;deepseek-r1:1.5b
$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;qwen3:4b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ollama官网有列出了所有可以直接下载使用的模型（可以使用 ollama pull 下载的）列表：https://ollama.com/library 。大部分时候，模型下载速度还可以。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama list&lt;/code&gt;查看下载的模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;list
NAME&lt;span class="w"&gt;                                     &lt;/span&gt;ID&lt;span class="w"&gt;              &lt;/span&gt;SIZE&lt;span class="w"&gt;      &lt;/span&gt;MODIFIED&lt;span class="w"&gt;     &lt;/span&gt;
deepseek-r1:1.5b&lt;span class="w"&gt;                         &lt;/span&gt;a42b25d8c10a&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.1&lt;span class="w"&gt; &lt;/span&gt;GB&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;weeks&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;     &lt;/span&gt;
qwen3:4b&lt;span class="w"&gt;                                 &lt;/span&gt;a383baf4993b&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.6&lt;span class="w"&gt; &lt;/span&gt;GB&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;weeks&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt;     &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;模型详情&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama show &amp;lt;model name&amp;gt;&lt;/code&gt;查看模型的详情，显示模型的参数量，上下文长度、量化信息等。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;deepseek-r1:1.5b
&lt;span class="w"&gt;  &lt;/span&gt;Model
&lt;span class="w"&gt;    &lt;/span&gt;architecture&lt;span class="w"&gt;        &lt;/span&gt;qwen2&lt;span class="w"&gt;     &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;parameters&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.8B&lt;span class="w"&gt;      &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;length&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;131072&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;embedding&lt;span class="w"&gt; &lt;/span&gt;length&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1536&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;quantization&lt;span class="w"&gt;        &lt;/span&gt;Q4_K_M&lt;span class="w"&gt;    &lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;Capabilities
&lt;span class="w"&gt;    &lt;/span&gt;completion&lt;span class="w"&gt;    &lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;Parameters
&lt;span class="w"&gt;    &lt;/span&gt;stop&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;｜begin▁of▁sentence｜&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;stop&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;｜end▁of▁sentence｜&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;stop&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;｜User｜&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;stop&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;｜Assistant｜&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;License
&lt;span class="w"&gt;    &lt;/span&gt;MIT&lt;span class="w"&gt; &lt;/span&gt;License&lt;span class="w"&gt;                    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;Copyright&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2023&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DeepSeek&lt;span class="w"&gt;    &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;删除模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama rm &amp;lt;model name&amp;gt;&lt;/code&gt; 删除模型。删除后，模型会从文件系统中删掉。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;qwen3:4b&lt;span class="w"&gt; &lt;/span&gt;
deleted&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;qwen3:4b &amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_4"&gt;运行模型&lt;/h3&gt;
&lt;p&gt;上面的命令都是对静态模型文件的增删和查看。下面看看如何运行和停止模型、与模型对话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama run &amp;lt;model name&amp;gt;&lt;/code&gt;运行 &lt;code&gt;deepseek-r1:1.5b&lt;/code&gt; 模型。如果指定运行的模型不存在，Ollama会尝试从官方仓库下载。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;deepseek-r1:1.5b
&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;hi
&amp;lt;think&amp;gt;

&amp;lt;/think&amp;gt;

Hello!&lt;span class="w"&gt; &lt;/span&gt;How&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;assist&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;today?&lt;span class="w"&gt; &lt;/span&gt;😊

&amp;gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Send&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;/?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;执行run命令后，Ollama提供了一个命令行的交互式对话界面，用于和模型进行对话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看运行中的模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再新开一个终端，使用&lt;code&gt;ollama ps&lt;/code&gt; 查看运行中的模型。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;ps
NAME&lt;span class="w"&gt;                &lt;/span&gt;ID&lt;span class="w"&gt;              &lt;/span&gt;SIZE&lt;span class="w"&gt;      &lt;/span&gt;PROCESSOR&lt;span class="w"&gt;    &lt;/span&gt;UNTIL&lt;span class="w"&gt;            &lt;/span&gt;
deepseek-r1:1.5b&lt;span class="w"&gt;    &lt;/span&gt;a42b25d8c10a&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.3&lt;span class="w"&gt; &lt;/span&gt;GB&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;CPU&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;minutes&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;now
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从命令的输出结果中看到，由于本机没有显卡，模型完全使用了CPU运行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;停止模型运行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ollama stop &amp;lt;model name&amp;gt;&lt;/code&gt;停止运行中的模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;deepseek-r1:1.5b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上就是ollama命令的基础用法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP访问Ollama&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以上都是通过ollama命令和Ollama server交互，之前说过Ollama客户端和server之间是通过HTTP协议通信。所以，也不是非得用ollama命令，通用的HTTP client也能向Ollama的server端发起请求，也能完成上面的任务。比如，查看运行中的模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:11434/api/ps&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;models&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deepseek-r1:1.5b&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;model&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deepseek-r1:1.5b&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;size&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1567668736&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;digest&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a42b25d8c10a841bd24724309898ae851466696a7d7f3a0a408b895538ccbc96&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;details&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;parent_model&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;format&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gguf&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;family&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qwen2&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;families&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qwen2&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;parameter_size&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.8B&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;quantization_level&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Q4_K_M&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;expires_at&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2025-06-12T18:17:00.179778313-04:00&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;size_vram&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;发起chat请求&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--request&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--url&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:11434/api/generate&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;content-type: application/json&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;model&amp;quot;: &amp;quot;qwen3:4b&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;prompt&amp;quot;: &amp;quot;讲个笑话&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;quot;format&amp;quot;: &amp;quot;json&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_5"&gt;环境变量&lt;/h3&gt;
&lt;p&gt;ollama命令行的参数很少，主要是通过环境变量控制server和client端的行为。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ollama server端环境变量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以通过环境变量控制Ollama server的行为，详细环境变量查看：https://github.com/ollama/ollama/blob/main/envconfig/config.go#L239&lt;/p&gt;
&lt;p&gt;下面是几个常用的server端环境变量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OLLAMA_MODELS：设置Ollama中本环境变量指定的目录加载模型&lt;/li&gt;
&lt;li&gt;OLLAMA_KEEP_ALIVE：模型在内存中的生存时间，到期之后会被stop，默认是5分钟&lt;/li&gt;
&lt;li&gt;OLLAMA_HOST：模型的监听IP地址，默认127.0.0.1，要对其它机器暴露的话，可以设置为 0.0.0.0&lt;/li&gt;
&lt;li&gt;OLLAMA_DEBUG：开启模型的DEBUG&lt;/li&gt;
&lt;li&gt;HTTP_PROXY：为Ollama设置HTTP代理&lt;/li&gt;
&lt;li&gt;OLLAMA_NUM_PARALLEL：客户端的请求并发数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于临时部署，比较喜欢下面这种方式使用环境变量&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OLLAMA_KEEP_ALIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;480m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;到达 &lt;code&gt;OLLAMA_KEEP_ALIVE&lt;/code&gt;指定的时间后，模型会停止运行，再对模型发起请求的时候会触发Ollama重新加载模型，这个加载速度比较慢，因此建议调高 &lt;code&gt;OLLAMA_KEEP_ALIVE&lt;/code&gt;这个环境变量。&lt;/p&gt;
&lt;p&gt;默认Ollama运行在 &lt;code&gt;127.0.0.1&lt;/code&gt;上，为了让其它节点访问，&lt;code&gt;OLLAMA_HOST&lt;/code&gt;可以修改为 0.0.0.0 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ollama没有认证机制，因此暴露给其它机器的时候要格外小心，可以结合网络防火墙、封装一层HTTP反向代理添加认证进行加固。&lt;strong&gt;千万不要直接暴露Ollama端口到外网环境。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ollama 客户端环境变量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ollama的客户端主要的环境变量也是 &lt;code&gt;OLLAMA_HOST&lt;/code&gt;，但是和server上的用途不同，客户端使用这个环境变量可以让 &lt;code&gt;ollama&lt;/code&gt;客户端连接该变量指向的Ollama server，这个变量默认是本地Ollama server，也即 http://127.0.0.1:11434。&lt;/p&gt;
&lt;h2 id="ai"&gt;AI代码助手&lt;/h2&gt;
&lt;p&gt;接下来，结合VSCode和 &lt;a href="https://www.continue.dev/"&gt;continue&lt;/a&gt; 插件，在VSCode中调用Ollama部署的模型，实现一个基础的AI代码助手。&lt;/p&gt;
&lt;p&gt;continue是专注于AI编程的IDE插件，支持VSCode 和JetBrains ，特点是可以方便的使用自己想要使用的模型。continue默认支持配置OpenAI、Deepseek、Gemini等大模型，也能支持Ollama、llama.cpp等开源工具部署的模型。灵活性很高，用户不必绑死在一个厂家上。 可以作为github copilot的一个替代方案。&lt;/p&gt;
&lt;h3 id="vscodecontinue"&gt;vscode和continue插件配置&lt;/h3&gt;
&lt;p&gt;不同版本的continue插件配置方法可能不一样，旧版本continue用的是json配置文件，现在改为了yaml。Continue的配置项参考：https://docs.continue.dev/reference ，提供了很丰富的配置样例 。 下面是基于Ollama的配置方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先为VSCode安装continue插件，完成后，在VSCode的左侧栏多了一个Continue的图标。&lt;/li&gt;
&lt;li&gt;点击Continue图标，左侧出现Continue的对话界面，点击对话框下方的&lt;code&gt;Select model -&amp;gt; Add Chat Model&lt;/code&gt;，弹出了添加模型的界面。窗口中添加的Ollama只能使用本地部署的Ollama，也即127.0.0.1，如果要使用远程的Ollama，需要编辑配置文件。&lt;/li&gt;
&lt;li&gt;点击界面最下方的 &lt;code&gt;config file&lt;/code&gt; 链接，进入Continue配置文件。配置文件路径实际是 ~/.continue/config.yaml ，也可以直接编辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在打开的Continue配置文件的&lt;code&gt;models&lt;/code&gt;配置块中添加如下的配置项：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;qwen2.5-coder&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;qwen2.5-coder:1.5b-base&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ollama&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;apiBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://192.168.1.10:11434&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;autocomplete&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;defaultCompletionOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.7&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;deepseek-r1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;deepseek-r1:1.5b&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ollama&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;apiBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://192.168.1.10:11434&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;chat&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面的配置中添加了两个模型，一个用于代码补齐（autocomplete），一个用于聊天（chat）。然后就可进行AI辅助编程了。&lt;/p&gt;
&lt;p&gt;因为希望代码补全能快一点，因此对autocomplete的实时要求比较高，由于没有显卡，这里使用参数量较小的 &lt;code&gt;qwen2.5-coder:1.5b-base&lt;/code&gt; ，速度快，但理解能力也就差一些。&lt;/p&gt;
&lt;p&gt;做完上面的配置，返回到vscode的编程窗口，打开一个代码文件，现在可以测试补全功能了。&lt;/p&gt;
&lt;h2 id="_6"&gt;制作模型文件&lt;/h2&gt;
&lt;p&gt;如果Ollama的模型库中没有提供想要使用的模型怎么办？&lt;/p&gt;
&lt;p&gt;常见的模型文件有 Safetensors 和 GGUF两种格式，前者是 &lt;a href="https://huggingface.co/"&gt;Hugging Face&lt;/a&gt;开发的 ，安全系数高。而GGUF是专为量化模型设计的，将模型架构、权重和超参数整合为单一文件，简化部署，主要用于本地推理。&lt;/p&gt;
&lt;p&gt;Ollama 默认支持gguf格式的模型，而Safetensors需要进行转换成gguf后，才能被Ollama使用。&lt;/p&gt;
&lt;h3 id="gguf"&gt;加载gguf格式模型&lt;/h3&gt;
&lt;p&gt;如果Ollama官方没有提供想要的模型，可以到 https://huggingface.co/ 搜索gguf格式的模型。比如&lt;code&gt;qwen1_5-7b-chat-q5_k_m.gguf&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;较小的模型只有一个 gguf 文件，可以先下载到本地，再创建文件&lt;code&gt;Modelfile&lt;/code&gt;，内容如下&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;FROM qwen1_5-7b-chat-q5_k_m.gguf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;启动Ollama的服务端后，加载模型，名字自定义为 qwen1_5-7b&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;qwen1_5-7b&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Modelfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看加载的模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;list
NAME&lt;span class="w"&gt;                &lt;/span&gt;ID&lt;span class="w"&gt;              &lt;/span&gt;SIZE&lt;span class="w"&gt;      &lt;/span&gt;MODIFIED&lt;span class="w"&gt;     &lt;/span&gt;
qwen1_5-7b:latest&lt;span class="w"&gt;    &lt;/span&gt;fd36fede3ee1&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;GB&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt; &lt;/span&gt;ago
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;之后就可以运行模型和对话了。&lt;/p&gt;
&lt;p&gt;如果是较大的模型，可能有多个 gguf文件，这个时候就要进行合并了。&lt;/p&gt;
&lt;h3 id="gguf_1"&gt;gguf文件合并&lt;/h3&gt;
&lt;p&gt;如果模型较大，可能有多个gguf，Ollama使用的时候，需要合并为一个gguf文件。&lt;a href="https://github.com/ggml-org/llama.cpp"&gt;llama.cpp&lt;/a&gt; 项目提供了合并的功能。建议使用llama.cpp项目的docker镜像来进行模型的合并。&lt;/p&gt;
&lt;p&gt;下载模型拆分后的各个部分，拆分后的文件格式是带编号的，比如下面的这些文件，文件名字中指定了每个文件在整个模型中的顺序。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-0000*
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7G&lt;span class="w"&gt; &lt;/span&gt;9月&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00001-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6G&lt;span class="w"&gt; &lt;/span&gt;9月&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00002-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6G&lt;span class="w"&gt; &lt;/span&gt;9月&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00003-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.4G&lt;span class="w"&gt; &lt;/span&gt;9月&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00004-of-00004.gguf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;接下来，将存放模型文件的本地目录映射到容器的/tmp/gguf 目录下，启动容器&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;./:/tmp/gguf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--entrypoint&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;ghcr.io/ggerganov/llama.cpp:full
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;进入容器后，检查文件没问题后执行模型合并操作，将上面4个分片合并成一个gguf文件：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;/tmp/gguf
qwen2.5-coder-7b-instruct-fp16-00001-of-00004.gguf
qwen2.5-coder-7b-instruct-fp16-00002-of-00004.gguf
qwen2.5-coder-7b-instruct-fp16-00003-of-00004.gguf
qwen2.5-coder-7b-instruct-fp16-00004-of-00004.gguf

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;
/app&lt;span class="w"&gt; &lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;./llama-gguf-split&lt;span class="w"&gt; &lt;/span&gt;--merge&lt;span class="w"&gt; &lt;/span&gt;/tmp/gguf/qwen2.5-coder-7b-instruct-fp16-00001-of-00004.gguf&lt;span class="w"&gt; &lt;/span&gt;/tmp/gguf/qwen2.5-coder-7b-instruct-fp16.gguf&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 合并&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;/tmp/gguf
total&lt;span class="w"&gt; &lt;/span&gt;29G
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7G&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00001-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6G&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00002-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6G&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00003-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.4G&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16-00004-of-00004.gguf
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt;  &lt;/span&gt;15G&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:20&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct-fp16.gguf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在存放模型碎片的 /tmp/gguf 目录下，最好不要有任何多余的文件，否则可能会执行失败&lt;/li&gt;
&lt;li&gt;要在 /app 目录下执行 &lt;code&gt;llama-gguf-split&lt;/code&gt;命令，否则会找不到依赖的库文件&lt;/li&gt;
&lt;li&gt;合并的gguf文件时，只需要指定分片中的第一个文件路径&lt;/li&gt;
&lt;li&gt;最后一个参数是要输出的gguf的文件名字&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;生成的文件差不多是原来各分片文件的大小之和。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;退出llama.cpp容器，编写Modelfile 加载合并之后的模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;FROM qwen2.5-coder-7b-instruct-fp16.gguf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;用Ollama创建模型&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;qwen2.5-coder-7b-instruct&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;models/Modelfile&lt;span class="w"&gt; &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;ollama&lt;span class="w"&gt; &lt;/span&gt;ls
NAME&lt;span class="w"&gt;                                          &lt;/span&gt;ID&lt;span class="w"&gt;              &lt;/span&gt;SIZE&lt;span class="w"&gt;      &lt;/span&gt;MODIFIED&lt;span class="w"&gt;       &lt;/span&gt;
qwen2.5-coder-7b-instruct:latest&lt;span class="w"&gt;              &lt;/span&gt;abf09b5f0c56&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GB&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;seconds&lt;span class="w"&gt; &lt;/span&gt;ago
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="safetensors"&gt;Safetensors 格式的模型&lt;/h3&gt;
&lt;p&gt;safetensors格式的模型需要adapter才能支持，没试过，可以参考官方文档。&lt;/p&gt;
&lt;p&gt;https://github.com/ollama/ollama/blob/main/docs/import.md&lt;/p&gt;
&lt;h2 id="_7"&gt;总结&lt;/h2&gt;
&lt;p&gt;Ollama作为开源的大语言模型本地化部署工具，为开发者提供了简单高效的解决方案。它支持主流开源模型的快速部署与管理，无需GPU即可在纯CPU环境下运行，既保障了数据隐私安全，又降低了使用门槛。通过命令行工具可实现模型的下载、运行和监控，并支持自定义模型加载。无论是隐私敏感场景的应用开发，还是本地AI方案的快速验证，Ollama都能提供灵活可靠的支撑。&lt;/p&gt;
&lt;p&gt;但是，Ollama也有非常严重的短板，那就是只支持单机部署和推理，无法集群化、规模化使用。不过，Ollama依旧是模型运行和验证领域的绝佳工具。即使我没有显卡，只有一台128G内存和24核CPU的老服务器，也能拿一些小参数的模型做做试验。&lt;/p&gt;
&lt;p&gt;个人以为，当下的大模型正如半个世纪以前的计算机，体格巨大，性能缓慢。随着技术发展，未来大模型也会和手机、电脑一样，性能更强、体积更小，走进千家万户。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/selfhosted-coding-assistant-with-ollama.html</guid>
      <pubDate>Sun, 15 Jun 2025 13:52:58 +0800</pubDate>
    </item>
    <item>
      <title>Linux内核模块编写和调试</title>
      <link>http://xnow.me/linuxes/compile-and-debug-kernel.html</link>
      <description>&lt;p&gt;&lt;img alt="light-in-the-sky" src="/usr/uploads/2025/05/1748178901.631036.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黄昏时分&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;接上一篇文章的内容 &lt;a href="/linuxes/compile-and-running-new-kernel.html"&gt;编译和运行新版本Linux内核&lt;/a&gt;，本文中，我们继续学习内核模块相关的基础知识。为了方便以后为Linux内核贡献代码，这次看看如何编写一个简单的内核模块，以及将其集成到Linux内核代码中，并使用GDB调试内核和我们写的内核模块。&lt;/p&gt;
&lt;p&gt;这篇文章紧接上文，因此会简化上文已经详细介绍的内核编译、QEMU等基础知识，如果过程中卡壳了，建议从上文  &lt;a href="/linuxes/compile-and-running-new-kernel.html"&gt;编译和运行新版本Linux内核&lt;/a&gt; 中寻找答案试试。也可以在评论区留言。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="light-in-the-sky" src="/usr/uploads/2025/05/1748178901.631036.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黄昏时分&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;接上一篇文章的内容 &lt;a href="/linuxes/compile-and-running-new-kernel.html"&gt;编译和运行新版本Linux内核&lt;/a&gt;，本文中，我们继续学习内核模块相关的基础知识。为了方便以后为Linux内核贡献代码，这次看看如何编写一个简单的内核模块，以及将其集成到Linux内核代码中，并使用GDB调试内核和我们写的内核模块。&lt;/p&gt;
&lt;p&gt;这篇文章紧接上文，因此会简化上文已经详细介绍的内核编译、QEMU等基础知识，如果过程中卡壳了，建议从上文  &lt;a href="/linuxes/compile-and-running-new-kernel.html"&gt;编译和运行新版本Linux内核&lt;/a&gt; 中寻找答案试试。也可以在评论区留言。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2 id="_1"&gt;一个简单的内核模块&lt;/h2&gt;
&lt;p&gt;Linux内核模块是一种动态可加载的内核对象（LKM, Loadable Kernel Module），用于在运行时扩展内核功能而无需重新编译或重启系统。这种模块化机制实现了核心内核与功能组件的解耦，使得文件系统、网络协议栈、设备驱动程序以及各类硬件支持能够以独立模块的形式存在。如果不模块化，而是将这些功能全部集成到内核中，不仅会导致内核过于庞大，还会有许多不需要的功能被默认加载。此外，每次功能变动都需要安装最新内核，并且必须重启系统。因此，动态加载的内核模块是Linux内核保持轻量和灵活的重要原因。&lt;/p&gt;
&lt;p&gt;内核模块通常使用C语言编写，本文中只需要编写一个简单的“Hello Word”演示程序，用于了解如何编写和使用内核模块。不会有太多的知识门槛。&lt;/p&gt;
&lt;h3 id="_2"&gt;编写内核模块&lt;/h3&gt;
&lt;p&gt;创建hello_module目录 ，编写如下的模块代码 &lt;code&gt;hello.c&lt;/code&gt;：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/init.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/module.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/kernel.h&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;MODULE_LICENSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GPL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;MODULE_AUTHOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Xnow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;MODULE_DESCRIPTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;A simple Linux kernel module.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;MODULE_VERSION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hello_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KERN_INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;********** %s **********&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hello_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KERN_INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Goodbye, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;module_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hello_init&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;module_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hello_exit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个和我们日常写的hello world还是挺不一样的：
+ &lt;code&gt;include&lt;/code&gt; 语句包含必要的头文件；
+ &lt;code&gt;MODULE_&lt;/code&gt; 行，明确这个模块的开源协议、作者、版本和描述等信息。
+ &lt;code&gt;staic char message[]&lt;/code&gt; 定义一个全局字符串message。
+ &lt;code&gt;hello_init()&lt;/code&gt; 和 &lt;code&gt;hello_exit()&lt;/code&gt; 两个函数分别输出 message和“Goodbye, World!\n” 字符串。
+ &lt;code&gt;module_init&lt;/code&gt; 和&lt;code&gt;module_exit&lt;/code&gt; 两个宏分别指定模块加载时的初始化函数和卸载时的清理函数。&lt;/p&gt;
&lt;p&gt;在同一目录下继续创建 Makefile，内容如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，Makefile中的缩进是 tab，而不是空格&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;obj-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hello.o

&lt;span class="nv"&gt;KDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/lib/modules/&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;uname&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;/build

&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;KDIR&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;M&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PWD&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;modules

&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;KDIR&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;M&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PWD&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;obj-m += hello.o&lt;/code&gt;：指定将hello.c 文件编译成hello.ko模块文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KDIR&lt;/code&gt;：指向当前系统的内核源码目录（通常是 /lib/modules/$(uname -r)/build）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make -C $(KDIR) M=$(PWD) modules&lt;/code&gt; ：&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-C $(KDIR):&lt;/code&gt; 切换到内核源码目录 &lt;code&gt;$(KDIR)&lt;/code&gt;，在该目录下执行 make。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M=$(PWD)&lt;/code&gt;: 指定模块源码的目录为当前目录 &lt;code&gt;$(PWD)&lt;/code&gt;。&lt;code&gt;$(PWD)&lt;/code&gt; 是一个环境变量，表示当前工作目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modules&lt;/code&gt;: 表示要编译模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里&lt;code&gt;$(KDIR)&lt;/code&gt; 指向当前运行中内核的源码目录。有时候，软件仓库里并没有提供当前运行内核开发包，可能是运行的内核版本已经过时了。这时，也可以安装略有版本差异的内核开发包试试，在 Makefile文件为KDIR指定具体的内核开发包版本的路径即可。&lt;/p&gt;
&lt;p&gt;比如我运行的内核是 &lt;code&gt;5.14.0-503.14.1.el9_5.x86_64&lt;/code&gt;，但是仓库里只能安装 &lt;code&gt;5.14.0-503.40.1.el9_5.x86_64&lt;/code&gt;。这时，可以安装&lt;code&gt;kernel-devel-5.14.0-503.40.1.el9_5.x86_64&lt;/code&gt; 和 &lt;code&gt;kernel-core-5.14.0-503.40.1.el9_5.x86_64&lt;/code&gt;这2个包。然后修改 KDIR， 修改为 &lt;code&gt;KDIR ?= /lib/modules/5.14.0-503.40.1.el9_5.x86_64/build&lt;/code&gt; 这个版本路径，也是一样能编译和加载的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里涉及到了&lt;code&gt;vermagic&lt;/code&gt;  变量和兼容性等问题，不做过多展开。实际上面举例的两个包是同一个版本的内核（5.14.0），只是补丁版本不一致。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="_3"&gt;编译和测试内核模块&lt;/h3&gt;
&lt;p&gt;先安装内核的开发包，之后执行编译和加载&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;kernel-devel-&lt;span class="k"&gt;$(&lt;/span&gt;uname&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kernel-core-&lt;span class="k"&gt;$(&lt;/span&gt;uname&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 编译&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# 检查编译结果&lt;/span&gt;
hello.c&lt;span class="w"&gt;  &lt;/span&gt;hello.ko&lt;span class="w"&gt;  &lt;/span&gt;hello.mod&lt;span class="w"&gt;  &lt;/span&gt;hello.mod.c&lt;span class="w"&gt;  &lt;/span&gt;hello.mod.o&lt;span class="w"&gt;  &lt;/span&gt;hello.o&lt;span class="w"&gt;  &lt;/span&gt;Makefile&lt;span class="w"&gt;  &lt;/span&gt;modules.order&lt;span class="w"&gt;  &lt;/span&gt;Module.symvers

$&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;hello.ko&lt;span class="w"&gt; &lt;/span&gt;
hello.ko:&lt;span class="w"&gt; &lt;/span&gt;ELF&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;LSB&lt;span class="w"&gt; &lt;/span&gt;relocatable,&lt;span class="w"&gt; &lt;/span&gt;x86-64,&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;SYSV&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;BuildID&lt;span class="o"&gt;[&lt;/span&gt;sha1&lt;span class="o"&gt;]=&lt;/span&gt;4ffdbb56e4c75c31b728d4fc1b07dc65c51159d8,&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;debug_info,&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;stripped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;编译完成后，生成了模块文件和各类临时文件， hello.ko 是需要的内核模块文件，也可以使用&lt;code&gt;modinfo&lt;/code&gt; 查看内核模块的信息。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;modinfo&lt;span class="w"&gt; &lt;/span&gt;./hello.ko&lt;span class="w"&gt; &lt;/span&gt;
filename:&lt;span class="w"&gt;       &lt;/span&gt;./hello.ko
version:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1
description:&lt;span class="w"&gt;    &lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;simple&lt;span class="w"&gt; &lt;/span&gt;Linux&lt;span class="w"&gt; &lt;/span&gt;kernel&lt;span class="w"&gt; &lt;/span&gt;module.
author:&lt;span class="w"&gt;         &lt;/span&gt;Xnow
license:&lt;span class="w"&gt;        &lt;/span&gt;GPL
rhelversion:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.5
srcversion:&lt;span class="w"&gt;     &lt;/span&gt;EE1A7DC8C21A87AB57A923B
depends:&lt;span class="w"&gt;        &lt;/span&gt;
retpoline:&lt;span class="w"&gt;      &lt;/span&gt;Y
name:&lt;span class="w"&gt;           &lt;/span&gt;hello
vermagic:&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.14.0-503.40.1.el9_5.x86_64&lt;span class="w"&gt; &lt;/span&gt;SMP&lt;span class="w"&gt; &lt;/span&gt;preempt&lt;span class="w"&gt; &lt;/span&gt;mod_unload&lt;span class="w"&gt; &lt;/span&gt;modversions&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;接下来试试加载和卸载模块&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;insmod&lt;span class="w"&gt; &lt;/span&gt;hello.ko&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 加载模块&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmesg&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;xxxxxx&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;**********&lt;span class="w"&gt; &lt;/span&gt;Hello,&lt;span class="w"&gt; &lt;/span&gt;World!&lt;span class="w"&gt; &lt;/span&gt;**********

$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;lsmod&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;hello&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 查看&lt;/span&gt;
hello&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rmmod&lt;span class="w"&gt; &lt;/span&gt;hello&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 卸载模块&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmesg&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;xxxxxx&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Goodbye,&lt;span class="w"&gt; &lt;/span&gt;World!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;insmod 用于加载内核模块&lt;/li&gt;
&lt;li&gt;模块代码中的 printk() 函数会将内容输出到 dmesg 内核日志文件中&lt;/li&gt;
&lt;li&gt;lsmod 和 cat /proc/modules 命令可以看到已经加载的模块&lt;/li&gt;
&lt;li&gt;rmmod 用于卸载模块，卸载时的函数也执行了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="_4"&gt;模块集成到内核并编译&lt;/h2&gt;
&lt;h3 id="_5"&gt;将模块集成到内核代码中&lt;/h3&gt;
&lt;p&gt;上面的步骤是动态的编译和加载内核模块。但是如何将模块内置到Linux源码中，并使用gdb对内核代码进行调试呢？&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;debug_linux&lt;/code&gt; 目录，这个目录将作为我们调试内核的主要目录。在目录下下载最新的Linux 的内核源码，解压。之后把上面的 hello.c 文件放到内核源码中的  &lt;code&gt;drivers&lt;/code&gt; 目录下。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/linux-kernel/v6.x/linux-6.14.6.tar.xz
$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xvf&lt;span class="w"&gt; &lt;/span&gt;linux-6.14.6.tar.xz
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;linux-6.14.6
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;../../hello_module/hello.c&lt;span class="w"&gt; &lt;/span&gt;drivers/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;修改 &lt;code&gt;drivers/Kconfig&lt;/code&gt;，在menu行的后面添加如下行&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config HELLO
    tristate &amp;quot;Hello World Module&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;tristate&lt;/code&gt;表示这个配置选项为三态选项，即编译内核时对该模块有三种选择：
+ &lt;code&gt;&amp;lt;*&amp;gt;&lt;/code&gt;，即y：编译并内置到内核中。
+ &lt;code&gt;&amp;lt;M&amp;gt;&lt;/code&gt;，编译为模块，可以在运行时加载。
+ &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt;，即n，表示不编译，不包含在内核中。&lt;/p&gt;
&lt;p&gt;除了 &lt;code&gt;tristate&lt;/code&gt;，还有bool类型的选项，有两种状态
+ &lt;code&gt;[*]&lt;/code&gt;：通常用于非模块化选项，表示启用某个内核配置选项。
+ &lt;code&gt;[ ]&lt;/code&gt;：表示不启用&lt;/p&gt;
&lt;p&gt;这几个选项类型在编译内核的&lt;code&gt;make menuconfig&lt;/code&gt;阶段中会看得到。&lt;/p&gt;
&lt;p&gt;继续修改 &lt;code&gt;drivers/Makefile&lt;/code&gt;，在文件内容起始处添加如下行：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;obj-$(CONFIG_HELLO)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hello.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CONFIG_HELLO&lt;/code&gt; 也就是对应上面Kconfig文件中配置的 &lt;code&gt;config HELLO&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;内核 drivers 目录的逻辑是十分严格的，所有模块都有各自的目录，各安其位，组织严密。我们直接将代码放到 drivers 目录的做法是临时测试的走捷径方法，实际中是不允许的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="hello"&gt;编译内核：启用hello模块和调试选项&lt;/h3&gt;
&lt;p&gt;接下来又是熟悉的编译内核环节。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;mrproper&lt;span class="w"&gt;  &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;x86_64_defconfig&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 生成针对 x86_64架构的默认配置文件&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;menuconfig&lt;span class="w"&gt;     &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在&lt;code&gt;make menuconfig&lt;/code&gt; 对Linux内核做如下定制。
+ 禁用内核的地址随机化，方便后续的内核调试
+ 关闭多媒体、声卡和虚拟化支持，加快编译速度
+ 开启内核debug模式，用于调试内核
+ 启用我们自己编写的Hello World模块&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;关闭地址随机化&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="nx"&gt;不然断点处无法停止&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
&lt;span class="nx"&gt;Processor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Randomize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kernel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;KASLR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;取消勾选&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Virtualization&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;

&lt;span class="nx"&gt;Device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Drivers&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;World&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;编写的模块&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="nx"&gt;启用&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Multimedia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;support&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sound&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;support&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;启用内核debug&lt;/span&gt;
&lt;span class="nx"&gt;Kernel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hacking&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Kernel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debugging&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Compile&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;checks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Rely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;toolchain&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;implicit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DWARF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;这个要修改&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GDB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kernel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debugging&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;上一步修改之后&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="nx"&gt;才有这个选项&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查下配置，&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(CONFIG_DEBUG_INFO|CONFIG_GDB_SCRIPTS|CONFIG_HELLO)=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.config
&lt;span class="nv"&gt;CONFIG_HELLO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;y&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 编写的测试模块&lt;/span&gt;
&lt;span class="nv"&gt;CONFIG_DEBUG_INFO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;y
&lt;span class="nv"&gt;CONFIG_GDB_SCRIPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面这3项必须全都是y才行，然后开始编译。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查编译成果：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;arch/x86/boot/bzImage
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;vmlinux

$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;arch/x86/boot/bzImage&lt;span class="w"&gt; &lt;/span&gt;vmlinux&lt;span class="w"&gt; &lt;/span&gt;../&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 都copy到debug_linux目录下&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;bzImage 是 Linux 内核的标准引导映像，用于启动系统。&lt;/li&gt;
&lt;li&gt;vmlinux 文件是未压缩的Linux内核映像，包含完整的内核代码、数据和符号信息，后续会用于调试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面新编译出的内核中开启了debug选项，也内置了我们自己编写的模块。继续往下就可以测试新版本的内核了。&lt;/p&gt;
&lt;h2 id="_6"&gt;调试内核&lt;/h2&gt;
&lt;p&gt;为方便调试，这次就基于initramfs启动新编译的内核，主要还是使用上篇文章 &lt;a href="/linuxes/compile-and-running-new-kernel.html"&gt;编译和运行新版本Linux内核&lt;/a&gt;  中用到的QEMU和busybox等技术。&lt;/p&gt;
&lt;h3 id="initramfs"&gt;制作 initramfs&lt;/h3&gt;
&lt;p&gt;安装busybox和QEMU&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 用 yum 安装qemu-kvm&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;qemu-kvm

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 编译busybox&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;debug_linux
$&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://busybox.net/downloads/busybox-1.37.0.tar.bz2
$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xvf&lt;span class="w"&gt; &lt;/span&gt;busybox-1.37.0.tar.bz2
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;busybox-1.37.0

$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;mrproper&lt;span class="w"&gt;   &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;menuconfig&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 一定要选择 Build static binary&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：编译busybox时，一定要选择编译成静态二进制文件。选项的路径是 &lt;code&gt;Settings --&amp;gt; Build Options --&amp;gt; [*] Build static binary (no shared libs)&lt;/code&gt;  。&lt;/p&gt;
&lt;p&gt;准备制作 initramfs  文件的目录结构。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;debug_linux
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;busybox-1.37.0/_install/*&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/

$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/init&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="s"&gt;mkdir /proc &amp;amp;&amp;amp; mount -t proc none /proc&lt;/span&gt;

&lt;span class="s"&gt;/bin/sh&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建Makefile&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;initramfs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;-print0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cpio&lt;span class="w"&gt; &lt;/span&gt;--null&lt;span class="w"&gt; &lt;/span&gt;-ov&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="o"&gt;=&lt;/span&gt;newc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gzip&lt;span class="w"&gt; &lt;/span&gt;-9&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;../initramfs.img

&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/usr/libexec/qemu-kvm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-kernel&lt;span class="w"&gt; &lt;/span&gt;bzImage&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-initrd&lt;span class="w"&gt; &lt;/span&gt;initramfs.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;256M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-nographic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-append&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;earlyprintk=serial,ttyS0 console=ttyS0 loglevel=8&amp;quot;&lt;/span&gt;

&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/usr/libexec/qemu-kvm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-kernel&lt;span class="w"&gt; &lt;/span&gt;bzImage&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-initrd&lt;span class="w"&gt; &lt;/span&gt;initramfs.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;256M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-machine&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;accel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-nographic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-append&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;earlyprintk=serial,ttyS0 console=ttyS0 loglevel=8 nokaslr&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个Makefile里3个目标：
+ initramfs：制作initramfs.img 镜像文件
+ run：用新的Linux内核和initramfs启动虚拟机。
+ debug：以调试模式启动QEMU虚拟机，启动的时候会暂停住，等待GDB的连接和指令
    + &lt;code&gt;-machine accel=tcg&lt;/code&gt; ，由于用的是 qemu-kvm，虚拟机会默认启用kvm，导致无法正常调试。&lt;code&gt;-machine accel=tcg&lt;/code&gt;配置关闭了kvm，使得调试得以进行。
    + -s：表示在1234端口接受GDB的调试连接。
    + -S：表示QEMU虚拟机会冻结CPU，直到远程的GDB输入相应控制命令。
+ nokaslr：用于禁用内核地址空间布局随机化（KASLR），以便内核的内存地址保持一致。KASLR 是用于随机化内核加载的内存地址的安全措施。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;在实际测试的过程中，用rmmod在宿主机上卸载&lt;/span&gt;&lt;span class="n n-Quoted"&gt;`kvm`&lt;/span&gt;&lt;span class="n"&gt;和&lt;/span&gt;&lt;span class="n n-Quoted"&gt;`kvm_intel`&lt;/span&gt;&lt;span class="n"&gt;两个内核模块后也可以顺利开展GDB调试。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="_7"&gt;测试运行&lt;/h3&gt;
&lt;p&gt;启动虚拟机前，先制作 initramfs.img 文件&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;initramfs
$&lt;span class="w"&gt; &lt;/span&gt;ls
bzImage&lt;span class="w"&gt;  &lt;/span&gt;initramfs_dir&lt;span class="w"&gt;  &lt;/span&gt;initramfs.img&lt;span class="w"&gt;  &lt;/span&gt;linux-6.14.6&lt;span class="w"&gt;  &lt;/span&gt;linux-6.14.6.tar.xz&lt;span class="w"&gt;  &lt;/span&gt;Makefile&lt;span class="w"&gt;  &lt;/span&gt;vmlinux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;运行 make run 正常启动QEMU。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在QEMU虚拟机的启动过程中，可以看到其中有一行输出了 &lt;code&gt;********** Hello， World! **********&lt;/code&gt; 。之后系统进入正常的shell环境中。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;Ctrl-a x&lt;/code&gt; 快捷键退出虚拟机。接下来调试kernel的启动过程。&lt;/p&gt;
&lt;h3 id="linux"&gt;运行和调试Linux内核&lt;/h3&gt;
&lt;p&gt;以调试模式启动QEMU虚拟机&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;debug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;启动后，QEMU 会监听在1234端口。打开另一个终端，使用gdb连接到QEMU的调试端口&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;debug_linux&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 这个目录下应该有 vmlinux 才行&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;gdb&lt;span class="w"&gt; &lt;/span&gt;-ex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;target remote 127.0.0.1:1234&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vmlinux&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="w"&gt; &lt;/span&gt;start_kernel&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 在linux的入口函数start_kernel上打断点&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="w"&gt; &lt;/span&gt;hello_init&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# 打在hello模块上打断点  &lt;/span&gt;

&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 继续向下运行，QEMU会停留在 Booting the kernel. 对应的是 start_kernel 函数&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 往下执行15行代码&lt;/span&gt;
&lt;span class="m"&gt;925&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;setup_nr_cpu_ids&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;command_line&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# 检查传递给内核的参数&lt;/span&gt;
&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0xffffffff8332e020&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;command_line&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;earlyprintk=serial,ttyS0 console=ttyS0 loglevel=8 nokaslr&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;linux_banner
&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0xffffffff823747e0&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;linux_banner&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Linux version 6.14.6 (xxx) (gcc (GCC)&lt;/span&gt;
&lt;span class="s2"&gt;.1-3), GNU ld version 2.35.2-54.el9) #2 SMP PREEMPT_DYNAMIC xxx\n&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;c

&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 检查变量值&lt;/span&gt;
&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, Linux!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 修改message的值&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 向下执行2行&lt;/span&gt;
&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;printk&lt;span class="o"&gt;(&lt;/span&gt;KERN_INFO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;********** %s **********\n&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;message&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;$(&lt;/span&gt;gdb&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;c&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 继续启动虚拟机&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;b&lt;/code&gt;、&lt;code&gt;c&lt;/code&gt;、&lt;code&gt;n&lt;/code&gt;、&lt;code&gt;p&lt;/code&gt;、&lt;code&gt;l&lt;/code&gt; 都是gdb调试的基础指令。&lt;/p&gt;
&lt;p&gt;上面的gdb命令先是检查了&lt;code&gt;command_line&lt;/code&gt;  和 &lt;code&gt;linux_banner&lt;/code&gt;变量，分别是qemu-kvm 命令中传递给内核的参数，与内核的版本号以及编译环境信息。&lt;/p&gt;
&lt;p&gt;之后又修改了自定义模块hello中的message变量的值，并检查修改之后的结果。修改之后向下执行2代码，观察QEMU中的情况，正好刚刚输出 &lt;code&gt;********** Hello Linux! **********&lt;/code&gt;。 在GDB中对变量message的修改生效了！&lt;/p&gt;
&lt;p&gt;通过上面的方法，可以一步步调试Linux内核的启动过程和执行过程，用于深入了解Linux的底层逻辑。&lt;/p&gt;
&lt;p&gt;以上就是用gdb调试内核的基本用法。&lt;/p&gt;
&lt;h2 id="_8"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文首先阐释了Linux内核模块的概念，并编写一个极简的hello代码，将其编译为内核模块，尝试加载到运行中的系统。之后又将代码集成到Linux源码中，编译到Linux内核里，使用gdb对该模块进行调试。&lt;/p&gt;
&lt;p&gt;以上就是关于内核模块编写、使用，以及开发Linux内核的基础知识了。&lt;/p&gt;
&lt;p&gt;如果想进一步了解关于Linux模块的相关内容，建议阅读 &lt;a href="https://sysprog21.github.io/lkmpg/"&gt;The Linux Kernel Module Programming Guide&lt;/a&gt; 这本小书。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/linuxes/compile-and-debug-kernel.html</guid>
      <pubDate>Sat, 24 May 2025 16:24:41 +0800</pubDate>
    </item>
    <item>
      <title>编译和运行新版本Linux内核</title>
      <link>http://xnow.me/linuxes/compile-and-running-new-kernel.html</link>
      <description>&lt;p&gt;&lt;img alt="outside-of-my-office" src="/usr/uploads/2025/05/1747625280.8771224.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;暮春时节，咖啡店外的街道郁郁葱葱。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对天天使用Linux系统的人来说，内核是个既神秘又强大的存在。编译内核也是探索和学习Linux的一种方式，虽然不大可能真的将自己编译的内核用在桌面或者生产系统，也不大有能力为Linux内核贡献代码，但是了解内核的基本组成还是挺有趣的。&lt;/p&gt;
&lt;p&gt;本文使用RockyLinux9作为编译机，编译最新版的Linux内核 6.14.6 版本，并编译busybox提供基础的shell环境。之后层层深入，从构建initramfs开始、制作rootfs、最后使用GRUB引导内核启动结束。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="outside-of-my-office" src="/usr/uploads/2025/05/1747625280.8771224.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;暮春时节，咖啡店外的街道郁郁葱葱。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对天天使用Linux系统的人来说，内核是个既神秘又强大的存在。编译内核也是探索和学习Linux的一种方式，虽然不大可能真的将自己编译的内核用在桌面或者生产系统，也不大有能力为Linux内核贡献代码，但是了解内核的基本组成还是挺有趣的。&lt;/p&gt;
&lt;p&gt;本文使用RockyLinux9作为编译机，编译最新版的Linux内核 6.14.6 版本，并编译busybox提供基础的shell环境。之后层层深入，从构建initramfs开始、制作rootfs、最后使用GRUB引导内核启动结束。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;&lt;strong&gt;基础环境：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编译的host系统：RockyLinux 9
Busybox版本：1.37.0&lt;br /&gt;
内核源码版本：6.14.6
QEMU的版本：qemu-kvm-9.0.0 &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不同发行版本的glibc等基础软件有差异，不是所有的系统都能编译最新版本内核，比如CentOS 7编译Linux 6的源码就有问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="busyboxlinux"&gt;编译Busybox和Linux&lt;/h2&gt;
&lt;p&gt;创建用于编译的目录结构，下载Busybox和Linux的源码，安装编译的依赖：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;build_linux
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;run_linux&lt;span class="w"&gt; &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build_linux
$&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://mirrors.aliyun.com/linux-kernel/v6.x/linux-6.14.6.tar.xz
$&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://busybox.net/downloads/busybox-1.37.0.tar.bz2
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;gcc&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;glibc-static&lt;span class="w"&gt; &lt;/span&gt;flex&lt;span class="w"&gt; &lt;/span&gt;bison&lt;span class="w"&gt; &lt;/span&gt;openssl-devel&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;ncurses-devel&lt;span class="w"&gt; &lt;/span&gt;elfutils-libelf-devel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;从Linux官网 kernel.org 下载Linux源码比较慢，这里选择从阿里云的镜像源里下载Linux源码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;解压和编译过程中，目录会大小膨胀，建议使用至少有 10GB 可用空间的分区来做编译和运行的环境。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build_linux 目录，用来保存下载和编译源码。&lt;/li&gt;
&lt;li&gt;run_linux 目录，用来保存Linux运行所需的文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，上面yum安装的基础依赖不一定就是全面的，如果后续编译遇到问题，可能还需要安装缺失的特定依赖。&lt;/p&gt;
&lt;h3 id="busybox"&gt;编译安装 busybox&lt;/h3&gt;
&lt;p&gt;Busybox 是非常知名的工具软件，里面实现了各种常用的Linux命令，可以提供一个轻量的shell环境，用于和kernel交互。&lt;/p&gt;
&lt;p&gt;为了方便使用，需要将 Busybox 编译成静态的二进制文件，之后运行时就没有依赖了， 编译静态二进制busybox的方法如下：&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;make menuconfig&lt;/code&gt; 时的交互选择界面中，勾选 &lt;code&gt;Build static binary (no shared libs)&lt;/code&gt;，路径是 &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;下面是具体的编译命令&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xvf&lt;span class="w"&gt; &lt;/span&gt;busybox-1.37.0.tar.bz2
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;busybox-1.37.0
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;mrproper&lt;span class="w"&gt;   &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;menuconfig&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 一定要选择 Build static binary&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;make mrproper&lt;/code&gt;，清理之前构建的中间产物，虽然我们是新下载解压的源码，但是编译前清理一下是个好习惯&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make menuconfig&lt;/code&gt; 交互式选择编译选项，只需要开启&lt;code&gt;Build static binary&lt;/code&gt;即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make -j $(nproc)&lt;/code&gt; 使用系统上所有的CPU核心进行编译，加快编译速度，也可以用&lt;code&gt;-j&lt;/code&gt;指定具体编译并发数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;验证编译出来的busybox二进制文件是否静态，是否能正常使用&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ldd&lt;span class="w"&gt; &lt;/span&gt;busybox&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;dynamic&lt;span class="w"&gt; &lt;/span&gt;executable
$&lt;span class="w"&gt; &lt;/span&gt;./busybox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果没报错，说明busybox的编译正常。&lt;/p&gt;
&lt;h3 id="linux"&gt;编译Linux内核&lt;/h3&gt;
&lt;p&gt;和上面的编译过程一样，编译Linux时也可以在&lt;code&gt;make menuconfig&lt;/code&gt;的过程中，自定义编译选项和模块。我一般会选择关闭虚拟化，多媒体和声卡，加快编译速度。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[ ] Virtualization  
Device Drivers
  &amp;lt; &amp;gt; Multimedia support
  &amp;lt; &amp;gt; Sound card support
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;做好选择之后退出并保存，会存为 .config 文件。接下来编译Linux源码的操作，和编译Busybox差不多。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xvf&lt;span class="w"&gt; &lt;/span&gt;linux-6.14.6.tar.xz
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;linux-6.14.6
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;mrproper&lt;span class="w"&gt;  &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;x86_64_defconfig&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 生成针对 x86_64架构的默认配置文件&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;menuconfig&lt;span class="w"&gt;     &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;arch/x86/boot/bzImage
arch/x86/boot/bzImage:&lt;span class="w"&gt; &lt;/span&gt;Linux&lt;span class="w"&gt; &lt;/span&gt;kernel&lt;span class="w"&gt; &lt;/span&gt;x86&lt;span class="w"&gt; &lt;/span&gt;boot&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;bzImage,&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.14.6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#...&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt;  &lt;/span&gt;arch/x86/boot/bzImage&lt;span class="w"&gt; &lt;/span&gt;
-rw-r--r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;13M&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:41&lt;span class="w"&gt; &lt;/span&gt;arch/x86/boot/bzImage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果make时遇到中报错：&lt;code&gt;X.509 certificates to be preloaded into the system blacklist keyring&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;需要进行如下的menuconfig配置，将如下路径中的具体证书路径清空，保存，重新编译即可。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Cryptographic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Certificates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checking&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;509&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preloaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 清空配置的证书路径&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;我的32核老服务器CPU在几分钟内就完成了编译，速度还是非常不错的。&lt;/p&gt;
&lt;p&gt;生成的 bzImage 是用于启动引导的内核文件，是压缩格式的，保存的路径为 arch/x86/boot/bzImage。这个文件非常袖珍，只有13M大小，真是神奇。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;make oldconfig&lt;/code&gt; 可以直接使用之前生成的 .config，而不用交互式选择模块。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="linux_1"&gt;使用新的Linux内核&lt;/h2&gt;
&lt;p&gt;QEMU是一款开源的仿真和虚拟化软件，可以模拟多种CPU架构和系统硬件。为简单起见，直接yum安装即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RockyLinux 9提供的qemu-kvm包集成了内核kvm虚拟化技术，可以提高QEMU模拟器的性能。
QEMU在不同发行版中内置的版本可能不一样，比如Debian 12中就推荐安装qemu-system-x86，并使用 qemu-system-x86_64 命令启动&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;qemu-kvm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;将之前编译生成的busybox和bzImage文件都复制到run_linux 目录下，后续的生成的启动文件也都放在 run_linux 目录下。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;run_linux
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;../build_linux/busybox-1.37.0/busybox&lt;span class="w"&gt; &lt;/span&gt;./
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;../build_linux/linux-6.14.6/arch/x86_64/boot/bzImage&lt;span class="w"&gt; &lt;/span&gt;./
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="initrd-initramfsrootfs"&gt;initrd 、 initramfs、rootfs的区别&lt;/h2&gt;
&lt;p&gt;Linux中initrd、initramfs、rootfs的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;initrd（initial ramdisk）：通常是一个压缩的块设备映像，是initramfs的前身，早期Linux中有所使用。加载到内存后需要挂载为一个临时文件系统。启动过程较长，现在已经很少使用。&lt;/li&gt;
&lt;li&gt;initramfs：基于内存的临时初始根文件系统，通常是cpio归档的格式，在系统启动过程中使用。在真正的rootfs被挂载之前，初始化硬件设备和加载必要的驱动（比如加载LVM模块），以便挂载真正的rootfs。系统启动过程结束后就不再需要，此时系统会切换到真正的rootfs。&lt;/li&gt;
&lt;li&gt;rootfs：这里指Linux系统磁盘上的根文件系统，包含了启动后系统运行所需的目录和文件，比如/bin、/etc、/dev、/proc等。通常作为系统的主文件系统，所有其他文件系统都会在其上挂载。rootfs通常包含了系统的长期存储数据。一般用硬盘等介质。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;GRUB的menuentry中可能还会使用initrd这个配置项名称，但指向的文件一般是nitramfs格式的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="initramfs"&gt;initramfs启动&lt;/h2&gt;
&lt;p&gt;有些简单的内核调试可能是不需要硬盘和持久化的，这时用initramfs就很方便。&lt;/p&gt;
&lt;p&gt;创建initramfs_dir，将busybox文件放到initramfs_dir/bin下，后续将initramfs_dir 目录打包成cpio格式的initramfs.img文件。等启动initramfs后，busybox在系统中的路径为 /bin/busybox。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/bin
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;busybox&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建启动文件&lt;code&gt;initramfs_dir/init&lt;/code&gt;、内容如下&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/busybox sh&lt;/span&gt;

/bin/busybox&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/proc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/bin/busybox&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;proc&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="w"&gt; &lt;/span&gt;/proc

/bin/busybox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello World.&amp;quot;&lt;/span&gt;
/bin/busybox&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;脚本中做了一些简单的初始化操作，如果有需要，也可以做的更复杂些。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先创建了 /proc 目录，然后挂载，有这个目录了就能运行ps命令&lt;/li&gt;
&lt;li&gt;然后输出了“Hello World”&lt;/li&gt;
&lt;li&gt;最后启动一个交互式的 &lt;code&gt;sh&lt;/code&gt;，提供给用户操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;init脚本中所有命令都是以busybox的方式运行的。下面为init文件添加可执行权限，它会在内核加载之后启动。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir/init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;为了方便测试，接下来创建Makefile，用来管理 initramfs和QEMU的启动命令&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;initramfs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;initramfs_dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;-print0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cpio&lt;span class="w"&gt; &lt;/span&gt;--null&lt;span class="w"&gt; &lt;/span&gt;-ov&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="o"&gt;=&lt;/span&gt;newc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gzip&lt;span class="w"&gt; &lt;/span&gt;-9&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;../initramfs.img

&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/usr/libexec/qemu-kvm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-kernel&lt;span class="w"&gt; &lt;/span&gt;bzImage&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-initrd&lt;span class="w"&gt; &lt;/span&gt;initramfs.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;256M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-nographic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-append&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;earlyprintk=serial,ttyS0 console=ttyS0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;initramfs&lt;/code&gt; 目标用于创建新的 initramfs.img 文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt; 目标用于启动QEMU&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-kernel&lt;/code&gt;和&lt;code&gt;-initrd&lt;/code&gt;参数指定了kernel文件和initramfs文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m&lt;/code&gt; 指定了QEMU虚拟机的内存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-nographic&lt;/code&gt; 指定不要启动图形界面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-append&lt;/code&gt; 指定虚拟机的串口等信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：makefile中的缩进是tab，不能是空格&lt;/p&gt;
&lt;p&gt;接下来正式打包 initramfs和运行编译好的Linux。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;initramfs&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 生成 initramfs.img 文件&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;run
...
Hello&lt;span class="w"&gt; &lt;/span&gt;World.
/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# busybox ls&lt;/span&gt;
bin&lt;span class="w"&gt;   &lt;/span&gt;dev&lt;span class="w"&gt;   &lt;/span&gt;init&lt;span class="w"&gt;  &lt;/span&gt;proc&lt;span class="w"&gt;  &lt;/span&gt;root

/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# busybox uname -a&lt;/span&gt;
Linux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;none&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.14.6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#1 SMP PREEMPT_DYNAMIC Sat May 17 10:40:54 EDT 2025 x86_64 GNU/Linux&lt;/span&gt;

/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# busybox ps -ef  | busybox  head -5&lt;/span&gt;
PID&lt;span class="w"&gt;   &lt;/span&gt;USER&lt;span class="w"&gt;     &lt;/span&gt;TIME&lt;span class="w"&gt;  &lt;/span&gt;COMMAND
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;init&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/busybox&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;/init
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;kthreadd&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;rcu_gp&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;rcu_par_gp&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到内核版本，以及1号进程都和我们预设的一样。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 命令前都需要加上 busybox 命令。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;退出QEMU的方式：先按Ctrl-a 后按x。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这样启动的QEMU虚拟机运行在内存中，重启后，系统中所有的配置和修改都会丢失。如果要对数据进行持久化，可以创建一块虚拟磁盘作为系统的rootfs。&lt;/p&gt;
&lt;p&gt;接下来测试rootfs的方案。&lt;/p&gt;
&lt;h2 id="rootfs"&gt;rootfs启动&lt;/h2&gt;
&lt;p&gt;为了摆脱&lt;code&gt;busybox ls&lt;/code&gt; 这种麻烦的命令行方式，接下来，完善rootfs的目录结构。进入busybox的编译目录，继续执行 &lt;code&gt;make install&lt;/code&gt;命令。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build_linux/busybox-1.37.0/
$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt;  &lt;/span&gt;_install/usr/bin/wget
lrwxrwxrwx.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;appweb&lt;span class="w"&gt; &lt;/span&gt;appweb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;:54&lt;span class="w"&gt; &lt;/span&gt;_install/usr/bin/wget&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;../../bin/busybox&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# wget命令是到busybox的软连接&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;会生成 &lt;code&gt;_install&lt;/code&gt; 目录，里面包含各类常用的命令文件，都以软连接的方式指向二进制文件 &lt;code&gt;bin/busybox&lt;/code&gt; 。里面的文件已经具备了基本的目录结构。&lt;/p&gt;
&lt;p&gt;创建rootfs目录和rootfs.img虚拟磁盘，对rootfs.img执行格式化和挂载，并将busybox新生成的 &lt;code&gt;_install&lt;/code&gt; 目录下的内容复制到 rootfs目录下。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;run_linux
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rootfs
$&lt;span class="w"&gt; &lt;/span&gt;dd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rootfs.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024k&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;mkfs.ext4&lt;span class="w"&gt; &lt;/span&gt;rootfs.img
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;rootfs.img&lt;span class="w"&gt; &lt;/span&gt;rootfs/
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$UID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rootfs&lt;span class="w"&gt;   &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;../build_linux/busybox-1.37.0/_install/*&lt;span class="w"&gt; &lt;/span&gt;rootfs/
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rootfs/&lt;span class="o"&gt;{&lt;/span&gt;proc,dev,etc&lt;span class="o"&gt;}&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;rootfs/etc/init.d/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;创建一个 50M 大小的文件，作为QEMU使用的虚拟磁盘&lt;/li&gt;
&lt;li&gt;先将虚拟磁盘格式化为ext4，再挂载到rootfs目录下&lt;/li&gt;
&lt;li&gt;将busybox的 &lt;code&gt;_install&lt;/code&gt; 目录下的内容复制到 rootfs下&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;继续为 rootfs 创建其它基础目录结构和文件。创建 rootfs/etc/inittab，启动后的路径为 /etc/inittab，定义系统的运行级别和该级别下启动的进程，内容如下&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="s s-Atom"&gt;sysinit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s s-Atom"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;rcS&lt;/span&gt;
&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="s s-Atom"&gt;respawn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s s-Atom"&gt;-/bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;sh&lt;/span&gt;
&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="s s-Atom"&gt;ctrlaltdel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;umount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="s s-Atom"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="s s-Atom"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;inittab文件的第1列通常是id，第2列通常是数字0-6，但是我们这个没这么复杂，仅指定了3个动作触发相应的任务&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sysinit 表示系统初始化阶段，指定执行 /etc/init.d/rcS 文件做初始化的任务&lt;/li&gt;
&lt;li&gt;respawn表示如果进程终止，则启动一个登录的/bin/sh&lt;/li&gt;
&lt;li&gt;ctrlaltdel 表示用户按下&lt;code&gt;Ctrl+Alt+Del&lt;/code&gt; 组合键时执行的命令，卸载所有文件系统，并以只读方式重新挂载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建 rootfs/etc/init.d/rcS，内容如下，会执行etc/fstab文件中指定的挂载操作&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#! /bin/sh&lt;/span&gt;

/bin/mount&lt;span class="w"&gt; &lt;/span&gt;-a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建 rootfs/etc/fstab ，内容如下，挂载 proc 文件系统&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;proc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;proc&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;defaults&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;给&lt;code&gt;etc/init.d/rcS&lt;/code&gt;赋予可执行权限。然后卸载 rootfs&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;rootfs/etc/init.d/rcS
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;rootfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;编写新的 &lt;code&gt;Makefile.rootfs&lt;/code&gt;，用于让QEMU以rootfs的方式启动内核&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;/usr/libexec/qemu-kvm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;-kernel&lt;span class="w"&gt; &lt;/span&gt;bzImage&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;256M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;-nographic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;-hda&lt;span class="w"&gt; &lt;/span&gt;rootfs.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;-append&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;root=/dev/sda rw earlyprintk=serial,ttyS0 console=ttyS0 loglevel=8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;整体上和initramfs的QEMU启动命令差不多，有细微差别
+ &lt;code&gt;-hda rootfs.img&lt;/code&gt; 将rootfs.img 作为额外硬盘挂载到QEMU虚拟机中。
+ 不需要 initrd 参数指定initramfs文件了。
+ &lt;code&gt;-append&lt;/code&gt;中指定了kernel的参数
    + root=/dev/sda：指定根文件系统的位置。rw 或 ro：指定根文件系统以读写或只读模式挂载。
    + 还可以指定 init 程序：init=/path/to/init。没指定的话，默认会尝试&lt;code&gt;/sbin/init&lt;/code&gt;、&lt;code&gt;/etc/init&lt;/code&gt;、&lt;code&gt;/bin/init&lt;/code&gt;、&lt;code&gt;/bin/sh&lt;/code&gt; 这些文件。&lt;/p&gt;
&lt;p&gt;启动rootfs的虚拟机，使用&lt;code&gt;make&lt;/code&gt; 命令的&lt;code&gt;-f&lt;/code&gt;选项指定为rootfs编写的Makefile文件Makefile.rootfs&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;Makefile.rootfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;之后进去系统就可以直接执行命令了&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# ls -l /bin/ls&lt;/span&gt;
lrwxrwxrwx&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;09&lt;/span&gt;:07&lt;span class="w"&gt; &lt;/span&gt;/bin/ls&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;busybox

~&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# df -hT&lt;/span&gt;
Filesystem&lt;span class="w"&gt;           &lt;/span&gt;Type&lt;span class="w"&gt;            &lt;/span&gt;Size&lt;span class="w"&gt;      &lt;/span&gt;Used&lt;span class="w"&gt; &lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;Use%&lt;span class="w"&gt; &lt;/span&gt;Mounted&lt;span class="w"&gt; &lt;/span&gt;on
/dev/root&lt;span class="w"&gt;            &lt;/span&gt;ext4&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;.8M&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.1M&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;.3M&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/
devtmpfs&lt;span class="w"&gt;             &lt;/span&gt;devtmpfs&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;.1M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;106&lt;/span&gt;.1M&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/dev

~&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# free -m&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;total&lt;span class="w"&gt;       &lt;/span&gt;used&lt;span class="w"&gt;       &lt;/span&gt;free&lt;span class="w"&gt;     &lt;/span&gt;shared&lt;span class="w"&gt;    &lt;/span&gt;buffers&lt;span class="w"&gt;     &lt;/span&gt;cached
Mem:&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="m"&gt;227&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;211&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
-/+&lt;span class="w"&gt; &lt;/span&gt;buffers/cache:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;213&lt;/span&gt;
Swap:&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到 ls 命令是到 busybox 文件的软连接。df和free命令也能正常显示&lt;/p&gt;
&lt;p&gt;如此就具备了一个基本的Linux的雏形。此时修改系统的文件和配置，内容就会被持久化到虚拟磁盘中了，实际上就是 rootfs.img 这个文件中。&lt;/p&gt;
&lt;h2 id="grub"&gt;配置GRUB&lt;/h2&gt;
&lt;p&gt;GRUB 是Linux系统中常用的引导加载程序，负责在计算机启动时加载操作系统内核并移交控制权。与之相关的配置文件一般位于/boot/ 目录下。 接下来创建新的虚拟磁盘，并使用parted划分为2个分区，一个用于GRUB引导，一个用于主分区。&lt;/p&gt;
&lt;p&gt;接下来，使用GRUB把我们的新Linux系统打造的更像样一些。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;dd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 50MB大小的磁盘&lt;/span&gt;

&lt;span class="c1"&gt;# 使用GPT分区&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;mklabel&lt;span class="w"&gt; &lt;/span&gt;gpt
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;mkpart&lt;span class="w"&gt; &lt;/span&gt;primary&lt;span class="w"&gt; &lt;/span&gt;1MiB&lt;span class="w"&gt; &lt;/span&gt;2MiB&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 第一个分区&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bios_grub&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# 标记为 BIOS Boot 分区&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;mkpart&lt;span class="w"&gt; &lt;/span&gt;primary&lt;span class="w"&gt; &lt;/span&gt;ext4&lt;span class="w"&gt; &lt;/span&gt;2MiB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 第二个分区，根分区&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt; &lt;/span&gt;print&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 查看分区表&lt;/span&gt;
Number&lt;span class="w"&gt;  &lt;/span&gt;Start&lt;span class="w"&gt;   &lt;/span&gt;End&lt;span class="w"&gt;     &lt;/span&gt;Size&lt;span class="w"&gt;    &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt;  &lt;/span&gt;Name&lt;span class="w"&gt;     &lt;/span&gt;Flags
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;1049kB&lt;span class="w"&gt;  &lt;/span&gt;2097kB&lt;span class="w"&gt;  &lt;/span&gt;1049kB&lt;span class="w"&gt;               &lt;/span&gt;primary&lt;span class="w"&gt;  &lt;/span&gt;bios_grub
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;2097kB&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;.4MB&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;.3MB&lt;span class="w"&gt;  &lt;/span&gt;ext4&lt;span class="w"&gt;         &lt;/span&gt;primary
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;创建了50M大小的disk.img文件，作为后续QEMU挂载使用的虚拟磁盘。用parted标记为gpt格式&lt;/li&gt;
&lt;li&gt;划定了2个分区，1M的那个用于BIOS Boot分区，剩余的空间是rootfs的根分区，并格式化为ext4&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将新创建的disk.img 也挂载到本地，这里使用 losetup 工具挂载。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;losetup&lt;span class="w"&gt; &lt;/span&gt;-Pf&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 挂载&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;losetup&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# 查看挂载，获取实际的盘符，可能是loop0，也可以是其他的编号&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;/dev/loop0*&lt;span class="w"&gt;  &lt;/span&gt;
/dev/loop0&lt;span class="w"&gt;  &lt;/span&gt;/dev/loop0p1&lt;span class="w"&gt;  &lt;/span&gt;/dev/loop0p2&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 可以看到虚拟磁盘的设备和分区的设备了&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkfs.ext4&lt;span class="w"&gt; &lt;/span&gt;/dev/loop0p2&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 谨慎确认后操作&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/loop0p2&lt;span class="w"&gt; &lt;/span&gt;disk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 必须确定 /dev/loop0 是我们新创建的虚拟盘，如果误操作到物理磁盘，会产生主机系统损坏的严重后果。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;挂载之后可以看到有&lt;code&gt;/dev/loop0p1&lt;/code&gt;和&lt;code&gt;/dev/loop0p2&lt;/code&gt; 两个分区（前提是之前没有挂载过文件）。 用mount进行的虚拟磁盘mount也会占用设备编号，我的环境中是 loop0，在你的设备中不一定，操作前注意用&lt;code&gt;losetup -a&lt;/code&gt; 检查下实际的盘符，并修改为自己环境的配置。&lt;/li&gt;
&lt;li&gt;挂载之后将根分区格式化为 ext4&lt;/li&gt;
&lt;li&gt;将格式化好的根分区挂载到本地新创建的disk目录，然后就可以往里面写数据了； &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面为新磁盘写入rootfs的内容，我们直接从之前的rootfs.img 里复制过来&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;rootfs.img&lt;span class="w"&gt; &lt;/span&gt;rootfs
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;rootfs/*&lt;span class="w"&gt; &lt;/span&gt;disk/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用&lt;code&gt;grub2-install&lt;/code&gt; 往虚拟磁盘中安装GRUB 2引导加载程序。并将 bzImage 内核文件复制到 boot 目录下&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;grub2-install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--target&lt;span class="o"&gt;=&lt;/span&gt;i386-pc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--boot-directory&lt;span class="o"&gt;=&lt;/span&gt;disk/boot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;/dev/loop0&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 操作前谨慎确认&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;bzImage&lt;span class="w"&gt; &lt;/span&gt;disk/boot/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 必须确定 /dev/loop0 是我们新创建的虚拟盘，如果误操作到物理磁盘，会产生主机系统损坏的严重后果。&lt;/p&gt;
&lt;p&gt;继续创建和编写GRUB 文件 &lt;code&gt;disk/boot/grub2/grub.cfg&lt;/code&gt;，指定加载模块和内核文件与内核参数。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; Begin /boot/grub2/grub.cfg
set default=0
set timeout=5

insmod part_gpt
insmod ext2
set root=(hd0,2)

menuentry &amp;quot;Linux 6.14.6&amp;quot; {
    linux       /boot/bzImage   root=/dev/sda2 ro console=ttyS0,115200n8 earlyprintk=serial
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;卸掉挂载，然后用QEMU启动系统&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;rootfs
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;disk
$&lt;span class="w"&gt; &lt;/span&gt;/usr/libexec/qemu-kvm&lt;span class="w"&gt; &lt;/span&gt;-hda&lt;span class="w"&gt; &lt;/span&gt;disk.img&lt;span class="w"&gt;  &lt;/span&gt;-nographic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;很快就能看到GRUB界面了！&lt;/p&gt;
&lt;p&gt;&lt;img alt="grub" src="/usr/uploads/2025/05/1747624904.746107.png" /&gt;&lt;/p&gt;
&lt;p&gt;GRUB结束后继续启动，就进入了内核启动阶段，最终就能看到一个不太完善的Linux系统了。&lt;/p&gt;
&lt;p&gt;至此，我们自己编译的当前最新版本Linux内核就成功的通过GRUB引导了。&lt;/p&gt;
&lt;h2 id="_1"&gt;总结&lt;/h2&gt;
&lt;p&gt;首先，我们编译了一个Linux内核，又编译了busybox为新内核提供简单而又基础的shell环境。通过制作initramfs在QEMU中快速启动新内核，方便快速测试和验证；通过虚拟磁盘构建rootfs的方式，实现虚拟机数据的持久化；最后，通过配置GRUB引导，使得这个新的系统看起来更加有模有样。&lt;/p&gt;
&lt;p&gt;但实际上，busybox提供的功能十分有限，缺乏大量的基础软件，远远不能作为真实的系统使用。如要想要进一步深入了解如何构建一个真实的Linux发行版，建议查看&lt;a href="https://www.linuxfromscratch.org/"&gt;Linux From Scratch&lt;/a&gt;，书中逐步讲解了如何从头编译一个完整Linux系统，这本书也不长，主要的篇幅都是编译过程，读起来并不吃力。&lt;/p&gt;
&lt;p&gt;本篇博客就到这里为止。后续还有一篇博客，将讲解如何开发一个简单而又无用的Linux内核模块，并使用gdb对其进行调试。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/linuxes/compile-and-running-new-kernel.html</guid>
      <pubDate>Mon, 19 May 2025 11:29:40 +0800</pubDate>
    </item>
    <item>
      <title>博客归来2</title>
      <link>http://xnow.me/sometalks/new-blog2.html</link>
      <description>&lt;p&gt;&lt;img alt="road-in-forest" src="/usr/uploads/2025/05/1746956655.103829.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2025年5月11日 拍摄于黄昏的浙大紫金港校园。正如那句老话，道路是曲折的，前景是光明的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个博客已经开通了很久，眼尖的朋友可能已经发现博客最近发生了重大改版。&lt;/p&gt;
&lt;p&gt;是的，近期我用 Flask 重写了本博客，替代用了好些年的Typecho。想想从大学时候写博客，用的wordpress，后续换到更轻量的typecho，其实都不算满意，主要原因就是不能随心所欲的定制，毕竟和PHP不熟。也看到很多人在用github或者其它静态页面工具搭建站点，但我更希望能自己写一套博客系统，而且必须用Python写，必须是动态的。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;img alt="road-in-forest" src="/usr/uploads/2025/05/1746956655.103829.jpg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2025年5月11日 拍摄于黄昏的浙大紫金港校园。正如那句老话，道路是曲折的，前景是光明的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个博客已经开通了很久，眼尖的朋友可能已经发现博客最近发生了重大改版。&lt;/p&gt;
&lt;p&gt;是的，近期我用 Flask 重写了本博客，替代用了好些年的Typecho。想想从大学时候写博客，用的wordpress，后续换到更轻量的typecho，其实都不算满意，主要原因就是不能随心所欲的定制，毕竟和PHP不熟。也看到很多人在用github或者其它静态页面工具搭建站点，但我更希望能自己写一套博客系统，而且必须用Python写，必须是动态的。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3 id="_1"&gt;起源&lt;/h3&gt;
&lt;p&gt;甚至很久之前就写好了前台相关的后端服务，用semantic-ui写出了一部分的页面。后来卡壳就停下了，不知道markdown的显示效果为什么这么差，不知道后端的文章编辑器该如何下手，以及各种想想都头大的问题。&lt;/p&gt;
&lt;p&gt;后来，我甚至粗略看了Vue、htmx，认真学了React相关的课程，但还是不得其门而入。前端就像一头猛虎拦住了去路。&lt;/p&gt;
&lt;h3 id="_2"&gt;干活&lt;/h3&gt;
&lt;p&gt;今年春天，忽然想起来要抛弃一切胡思乱想，就用传统的Bootstrap，服务端渲染，加一点jquery，应该就能满足这个博客系统的要求了。&lt;/p&gt;
&lt;p&gt;说干就干，看了1个小时的bootstrap视频学习网站布局，然后开始动手，将原来的前端代码全部删除，用bootstrap搭出一个现在流行的极简样式。居然没有那么复杂。&lt;/p&gt;
&lt;p&gt;从3月下旬开始，一写就停不下来了：周末写、熬夜写、工作日的午休时间写。写完前台写后台，写完后端写前端，整完了代码整自动化发布。把原计划要读的书都搁置了，全部的休息时间用来写代码。期间还发生了家里homelab主服务器sd卡损坏，备份不完整导致PostgreSQL 数据只能回滚到4个月前的事情，把许多重要服务做了紧急迁移恢复。&lt;/p&gt;
&lt;p&gt;一路上，遇到许多之前没考虑到问题，也用一些奇思妙想解决了奇怪的问题，有时候做梦都在写。当然，AI在这个过程中也助力不少，虽然没有用copilot，但对于拿不准的前端问题，问AI的话，往往有不错的效果。&lt;/p&gt;
&lt;h3 id="_3"&gt;发布&lt;/h3&gt;
&lt;p&gt;终于，五一劳动节期间，将新版本的博客系统推出了。&lt;/p&gt;
&lt;p&gt;当然，这套系统不论是用户测体验，还是管理后台功能，或是代码质量与性能，都还有许多需要优化的地方。这次先发布初版，后续慢慢优化。&lt;/p&gt;
&lt;p&gt;重要的是内容，博客已经停更很久了。主要原因是当我切换到Joplin笔记软件后，体验非常舒适，许多笔记我都记录在上面了，但是由于对原来的博客系统不是很喜欢，也就没有继续整理发表的心情了。这次一定要坚持下去，近期争取每周发表一篇，以后也要至少每月1篇。&lt;strong&gt;自研的这套博客系统会给我持续更新下去的动力&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;虽然，我没有成为全栈工程师的野心，对前端依旧没有多少兴趣，但是掌握一些前端才能具备开发完整产品的能力。因此，这个博客，就是我的第一个作品。&lt;/p&gt;
&lt;h3 id="_4"&gt;总结&lt;/h3&gt;
&lt;p&gt;或许博客这种载体已经过时了，但是，就像我homelab上自建的笔记、RSS、即时通讯、开发套件、影音服务一样，要做自己信息的主人，特立独行，不要被时代潮流里过剩的信息淹没。&lt;/p&gt;
&lt;p&gt;2014年的 &lt;a href="/sometalks/myblog-back.html"&gt;博客归来&lt;/a&gt;  是一次重大的变化，那次换了域名和开源系统。这次将文章写在自己的代码上，要更长久的坚持下去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;博观而约取，厚积而薄发&lt;/strong&gt;， 勉之！&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/sometalks/new-blog2.html</guid>
      <pubDate>Sun, 11 May 2025 14:32:36 +0800</pubDate>
    </item>
    <item>
      <title>Vagrant和Vagrantfile入门</title>
      <link>http://xnow.me/ops/vagrant-and-vagrantfile.html</link>
      <description>&lt;p&gt;&lt;a href="https://www.Vagrantup.com/"&gt;Vagrant&lt;/a&gt; 是由hashicorp公司（该公司还开源了Consul、Terraform等工具）开源的用于构建和管理虚拟机环境的工具，对于快速构建开发环境十分有用。Vagrant使用Vagrantfile进行虚拟机编排，和docker-compose有些类似，只不过Vagrant管理的对象是虚拟机，Vagrant支持VirtualBox、 VMware Fusion 和 Hyper-V  等许多虚拟化产品。本文中基于VirtualBox做演示。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;a href="https://www.Vagrantup.com/"&gt;Vagrant&lt;/a&gt; 是由hashicorp公司（该公司还开源了Consul、Terraform等工具）开源的用于构建和管理虚拟机环境的工具，对于快速构建开发环境十分有用。Vagrant使用Vagrantfile进行虚拟机编排，和docker-compose有些类似，只不过Vagrant管理的对象是虚拟机，Vagrant支持VirtualBox、 VMware Fusion 和 Hyper-V  等许多虚拟化产品。本文中基于VirtualBox做演示。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;对于开发人员来说，Vagrant可以快速构建一致性的开发环境，并通过分享Vagrantfile快速创建相同的开发环境，是一个很值得了解的工具。Vagrantfile是Vagrant的核心，直到vagrant如何创建虚拟机。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实践环境&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统：Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;Vagrant版本：Vagrant 2.3.4&lt;/li&gt;
&lt;li&gt;VirtualBox版本：7.0.6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;各操作系统上安装Vagrant的方法见官方链接 &lt;a href=""&gt;[Install | Vagrant | HashiCorp Developer](https://developer.hashicorp.com/vagrant/downloads)&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="vagrant"&gt;为什么要用Vagrant？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对运维工程师来说，学习新软件的第一步肯定是要尝试自行搭建一套环境，尤其是Kubernetes、Ceph等大型软件，一般需要多台机器构建分布式集群。使用Vagrantfile可以快速定制多虚拟机组成的服务器基础环境。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对研发和测试工程师来说，通过干净的虚拟机环境测试，有也助于复现、排除和定位软件问题。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="vagrant_1"&gt;Vagrant基础&lt;/h2&gt;
&lt;h3 id="vagrant-box"&gt;Vagrant box&lt;/h3&gt;
&lt;p&gt;box的概念可以对应docker镜像，也即虚拟机镜像，box是本机全局共享的。Vagrant提供了类似docker hub的在线镜像平台：&lt;a href="https://app.vagrantup.com"&gt;https://app.vagrantup.com&lt;/a&gt; ，可以选择自己需要的基础镜像。&lt;/p&gt;
&lt;p&gt;执行&lt;code&gt;vagrant box&lt;/code&gt; 命令可以查看和box相关的操作，常用的有如下几个子命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add：在线下载box&lt;/li&gt;
&lt;li&gt;list：列出当前机器上的box&lt;/li&gt;
&lt;li&gt;remove：删除box&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;默认从官方下载box比较慢，许多发行版都提供了用于vagrant的box文件，可以从国内的镜像源下载并添加到vagrant即可使用。比如，创建 &lt;code&gt;ubuntu/xenial&lt;/code&gt; 这个box。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;ubuntu/xenial&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;https://mirrors.ustc.edu.cn/ubuntu-cloud-images/xenial/20210928/xenial-server-cloudimg-amd64-vagrant.box
$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;box&lt;span class="w"&gt; &lt;/span&gt;list
ubuntu/xenial&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="vagrant_2"&gt;常用的Vagrant 指令&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;vagrant init&lt;/code&gt; 是初始化Vagrantfile的命令&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant up&lt;/code&gt;  根据当前目录下的Vagrantfile创建和启动虚拟机&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant destroy&lt;/code&gt;  销毁Vagrantfile创建的虚拟机&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant ssh/scp&lt;/code&gt; ssh相关的子命令，可以使用ssh协议登录虚拟机，scp子命令则可以在虚拟机和物理机之间拷贝文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant status/global-status&lt;/code&gt;：status查看当前环境下的虚拟机状态，global-status 则可以查看本机上所有的虚拟机状态。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant halt&lt;/code&gt; 关闭虚拟机&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant reload&lt;/code&gt;重启，基于更新后Vagrantfile更新虚拟机&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vagrant port&lt;/code&gt; 查看虚拟机和物理机端口映射&lt;/p&gt;
&lt;p&gt;以下是基础的vagrant 指令操作&lt;/p&gt;
&lt;p&gt;创建Vagrantfile，&lt;code&gt;-m&lt;/code&gt; 参数用于创建简化的Vagrantfile&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/tmp/vagrant
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/vagrant
&lt;span class="gp"&gt;$ &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;generic/centos7&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt;   &lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;Vagrantfile&lt;span class="w"&gt;      &lt;/span&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;-*-&lt;span class="w"&gt; &lt;/span&gt;mode:&lt;span class="w"&gt; &lt;/span&gt;ruby&lt;span class="w"&gt; &lt;/span&gt;-*-
&lt;span class="gp"&gt;# &lt;/span&gt;vi:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ft&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby&lt;span class="w"&gt; &lt;/span&gt;:

&lt;span class="go"&gt;Vagrant.configure(&amp;quot;2&amp;quot;) do |config|&lt;/span&gt;
&lt;span class="go"&gt;  config.vm.box = &amp;quot;generic/centos7&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;启动Vagrantfile定义的虚拟机，默认的provider一般是virtualbox，也可以使用--provider指定。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;--provider&lt;span class="o"&gt;=&lt;/span&gt;virtualbox&lt;span class="w"&gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看vagrant管理的虚拟机， 默认创建的虚拟机名叫default&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagtrant&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;
Current&lt;span class="w"&gt; &lt;/span&gt;machine&lt;span class="w"&gt; &lt;/span&gt;states:
default&lt;span class="w"&gt;                   &lt;/span&gt;running&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看vagrant的端口映射，将虚拟机的22号端口映射到本地2222端口&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;guest&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2222&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;host&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;登录到默认虚拟机&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;default
&lt;span class="o"&gt;[&lt;/span&gt;vagrant@centos7&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;实际上等价于  &lt;code&gt;ssh -i .vagrant/machines/default/virtualbox/private_key -p 2222 vagrant@127.0.0.1&lt;/code&gt; ，登录映射到本地的 2222端口。&lt;code&gt;.vagrant&lt;/code&gt; 目录下有许多vagrant生产的文件。&lt;/p&gt;
&lt;p&gt;关闭虚拟机&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;halt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Attempting&lt;span class="w"&gt; &lt;/span&gt;graceful&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;VM...
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;vagrant-hostsupdater&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;hosts&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;suspend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;关闭后，虚拟机的状态变为 poweroff&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;
Current&lt;span class="w"&gt; &lt;/span&gt;machine&lt;span class="w"&gt; &lt;/span&gt;states:
default&lt;span class="w"&gt;                   &lt;/span&gt;poweroff&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 虚拟机状态变为poweroff&lt;/span&gt;

The&lt;span class="w"&gt; &lt;/span&gt;VM&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;powered&lt;span class="w"&gt; &lt;/span&gt;off.&lt;span class="w"&gt; &lt;/span&gt;To&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;VM,&lt;span class="w"&gt; &lt;/span&gt;simply&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;销毁虚拟机&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;destroy&lt;span class="w"&gt;     &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Are&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;sure&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;want&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;destroy&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VM?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;y/N&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;Destroying&lt;span class="w"&gt; &lt;/span&gt;VM&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;associated&lt;span class="w"&gt; &lt;/span&gt;drives...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;销毁之后，虚拟机的状态变为 &lt;code&gt;not created&lt;/code&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;
Current&lt;span class="w"&gt; &lt;/span&gt;machine&lt;span class="w"&gt; &lt;/span&gt;states:

default&lt;span class="w"&gt;                   &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;virtualbox&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 状态变为 not created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;以上创建的虚拟机，在virtualbox的GUI界面上也能看得见，也可以使用virtualbox进行管理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;到这里为止基本上就完成了Vagrant的入门，也没什么了不起的，对吧？别着急，Vagrant的精华在Vagrantfile，精彩的在后面。&lt;/p&gt;
&lt;h2 id="vagrantfile"&gt;定制Vagrantfile&lt;/h2&gt;
&lt;p&gt;在上一部分中，仅仅使用了Vagrant生成的默认Vagrantfile，Vagrantfile使用的是ruby的语法，非常灵活和强大，我也不会ruby，就老老实实依葫芦画瓢按照需求来写。&lt;/p&gt;
&lt;p&gt;Vagrant的&lt;code&gt;provisioning&lt;/code&gt; 功能可以在&lt;code&gt;vagrant up&lt;/code&gt; 过程中执行安装软件、运行指定命令等，用来更好的按需定制化系统。&lt;a href="https://developer.hashicorp.com/vagrant/docs/provisioning"&gt;Provisioning | Vagrant | HashiCorp Developer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vagrant 有插件机制，支持的插件列表见链接 &lt;a href="https://github.com/hashicorp/vagrant/wiki/Available-Vagrant-Plugins"&gt;Available-Vagrant-Plugins&lt;/a&gt; ，Vagrant可以对plugin执行&lt;code&gt;install&lt;/code&gt;、&lt;code&gt;uninstall&lt;/code&gt;、&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;update&lt;/code&gt;等操作。&lt;/p&gt;
&lt;p&gt;比如，安装 &lt;a href="https://github.com/oscar-stack/vagrant-hosts"&gt;vagrant-hosts&lt;/a&gt; 插件，用于管理虚拟机的&lt;code&gt;/etc/hosts&lt;/code&gt;文件&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;vagrant-hosts
&lt;span class="gp"&gt;$ &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;list
&lt;span class="go"&gt;vagrant-hosts (2.9.0, global)&lt;/span&gt;
&lt;span class="go"&gt;vagrant-scp (0.5.9, global)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以下是一个比较完整的定制化Vagrantfile，涉及了网络、磁盘、内存、CPU、系统初始化等方面的定制，基于这个Vagrantfile，将Vagrantfile修改成自己需要的样子。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;

&lt;span class="vg"&gt;$init_script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="sh"&gt;&amp;#39;&lt;/span&gt;&lt;span class="dl"&gt;SCRIPT&lt;/span&gt;&lt;span class="sh"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="sh"&gt;     setenforce 0&lt;/span&gt;
&lt;span class="sh"&gt;     sed -i &amp;#39;s/SELINUX=enforcing/SELINUX=disabled/&amp;#39; /etc/selinux/config&lt;/span&gt;
&lt;span class="sh"&gt;     unlink /etc/localtime&lt;/span&gt;
&lt;span class="sh"&gt;     ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime&lt;/span&gt;
&lt;span class="dl"&gt;SCRIPT&lt;/span&gt;

&lt;span class="vg"&gt;$ext_mount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="sh"&gt;&amp;#39;&lt;/span&gt;&lt;span class="dl"&gt;SCRIPT&lt;/span&gt;&lt;span class="sh"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="sh"&gt;  parted -s /dev/sdb mklabel gpt mkpart primary ext4 0% 100%&lt;/span&gt;
&lt;span class="sh"&gt;  mkfs.ext4  /dev/sdb1&lt;/span&gt;
&lt;span class="sh"&gt;  mkdir /data&lt;/span&gt;
&lt;span class="sh"&gt;  mount -t ext4 /dev/sdb1 /data&lt;/span&gt;
&lt;span class="sh"&gt;  echo &amp;quot;/dev/sdb1 /data ext4 defaults 0 0&amp;quot; &amp;gt;&amp;gt; /etc/fstab&lt;/span&gt;
&lt;span class="dl"&gt;SCRIPT&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;generic/centos7&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shell&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;systemctl stop firewalld.service&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shell&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$init_script&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shell&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;$ext_mount&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.vimrc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.vimrc&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hosts&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.56.11&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node-1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.56.12&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node-2&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.56.13&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node-3&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:disk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;10GB&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;virtualbox&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;modifyvm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--memory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1024&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;modifyvm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--cpus&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node-1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;private_network&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192.168.56.11&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node-2&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;private_network&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192.168.56.12&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node-3&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;private_network&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192.168.56.13&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;启动上面定义的Vagrantfile&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VAGRANT_EXPERIMENTAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disks&lt;span class="w"&gt; &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;环境变量 &lt;code&gt;VAGRANT_EXPERIMENTAL=disks&lt;/code&gt;的作用是，本Vagrantfile中使用的disks相关功能还在实验阶段，如果不加环境变量，功能将无法正常使用。如果更新了Vagrantfile，在使用&lt;code&gt;vagrant reload&lt;/code&gt;的时候也需要加上这个环境变量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;接下来解释下这个Vagrantfile每一部分所做的事情。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先定义了2个变量&lt;code&gt;init_script&lt;/code&gt;和&lt;code&gt;ext_mount&lt;/code&gt;，自定义服务器创建时需要执行的初始化操作。这种多行字符串变量的定义方法叫 Heredoc，Bash中也有类似的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;do/end&lt;/code&gt; 相当于 &lt;code&gt;{}&lt;/code&gt;，也可以用&lt;code&gt;{}&lt;/code&gt;替换 ，用于定义一段代码块。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;test do |x| ... end&lt;/code&gt;，在这种语句结构中，两个竖线中的 &lt;code&gt;x&lt;/code&gt; 是局部变量，用于接收前面语句抛出来的值。 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;code&gt;config.vm.provision&lt;/code&gt; 几行，使用了&lt;a href="https://developer.hashicorp.com/vagrant/docs/provisioning/shell"&gt;shell&lt;/a&gt;和&lt;a href="https://developer.hashicorp.com/vagrant/docs/provisioning/file"&gt;file&lt;/a&gt;这两个内置的Provisioner。在这个过程中可以定义许多主机初始化操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;code&gt;config.vm.provision "hosts"&lt;/code&gt; 一行中，使用了前面安装的&lt;a href="https://github.com/oscar-stack/vagrant-hosts"&gt;vagrant-hosts&lt;/a&gt;插件，为所有机器都添加了&lt;code&gt;/etc/hosts&lt;/code&gt;静态解析。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;(1..3).each do |i| ... end&lt;/code&gt;  这里是个循环，分别将1、2、3赋值给i，使用这个变量定义了&lt;code&gt;node-1&lt;/code&gt;、&lt;code&gt;node-2&lt;/code&gt;和&lt;code&gt;node-3&lt;/code&gt;这三台主机。并定制了主机的内存、CPU资源和额外data硬盘等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;node.vm.network "private_network"&lt;/code&gt; 这3处代码则分别定义了3个节点的额外网卡和ip。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;剩余的语句基本上就是赋值和函数执行一类的，没什么好说的，依葫芦画瓢即可，遇到特殊需求再google搜索。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;启动Vagrant之后，继续验证主机是否符合预期。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="go"&gt;Current machine states:&lt;/span&gt;

&lt;span class="go"&gt;node-1                    running (virtualbox)&lt;/span&gt;
&lt;span class="go"&gt;node-2                    running (virtualbox)&lt;/span&gt;
&lt;span class="go"&gt;node-3                    running (virtualbox)&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;node-3
&lt;span class="go"&gt;Last login: Tue Feb 21 23:37:03 2023 from 10.0.2.2&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;df&lt;span class="w"&gt; &lt;/span&gt;-hT&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb1&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="go"&gt;Filesystem     Type  Size  Used Avail Use% Mounted on&lt;/span&gt;
&lt;span class="go"&gt;/dev/sdb1      ext4  9.8G   37M  9.2G   1% /data&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;ip&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;eth1
&lt;span class="go"&gt;3: eth1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000&lt;/span&gt;
&lt;span class="go"&gt;    link/ether 08:00:27:6d:39:57 brd ff:ff:ff:ff:ff:ff&lt;/span&gt;
&lt;span class="go"&gt;    inet 192.168.56.13/24 brd 192.168.56.255 scope global noprefixroute eth1&lt;/span&gt;
&lt;span class="go"&gt;       valid_lft forever preferred_lft forever&lt;/span&gt;
&lt;span class="go"&gt;    inet6 fe80::a00:27ff:fe6d:3957/64 scope link &lt;/span&gt;
&lt;span class="go"&gt;       valid_lft forever preferred_lft forever&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;nproc&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="go"&gt;2&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;free&lt;span class="w"&gt; &lt;/span&gt;-m
&lt;span class="go"&gt;              total        used        free      shared  buff/cache   available&lt;/span&gt;
&lt;span class="go"&gt;Mem:            990         130         683          12         176         708&lt;/span&gt;
&lt;span class="go"&gt;Swap:          2047           0        2047&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-3&lt;span class="w"&gt; &lt;/span&gt;/etc/hosts
&lt;span class="go"&gt;192.168.56.11 node-1&lt;/span&gt;
&lt;span class="go"&gt;192.168.56.12 node-2&lt;/span&gt;
&lt;span class="go"&gt;192.168.56.13 node-3&lt;/span&gt;

&lt;span class="gp"&gt;[vagrant@node-3 ~]$ &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;firewalld
&lt;span class="go"&gt;● firewalld.service - firewalld - dynamic firewall daemon&lt;/span&gt;
&lt;span class="go"&gt;   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到主机的初始化完全符合我们的定义。&lt;/p&gt;
&lt;h2 id="vagrant_3"&gt;为什么要使用Vagrant？&lt;/h2&gt;
&lt;p&gt;我个人总结Vagrant有如下几点优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Vagrant十分强大，也有很丰富的插件可以选择：&lt;a href="https://github.com/hashicorp/vagrant/wiki/Available-Vagrant-Plugins"&gt;Available Vagrant Plugins · hashicorp/vagrant Wiki · GitHub&lt;/a&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;十分灵活的配置方法，且易于分享，简化系统初始化和配置过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虚拟机可以弥补某些测试场景下Docker的不足，比如需要特定的操作系统版本和内核。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于网络、集群搭建等需要隔离和干净环境的需求，vagrant是比较方便。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/ops/vagrant-and-vagrantfile.html</guid>
      <pubDate>Wed, 22 Feb 2023 00:08:00 +0800</pubDate>
    </item>
    <item>
      <title>VimScript：退出文件时自动关闭各种缓冲区(Buffer)</title>
      <link>http://xnow.me/programs/vimscript-autoclose-buffers.html</link>
      <description>&lt;p&gt;经过调教的vim，肯定有很多插件，比如nerdtree，python-mode等等，很多插件在使用过程中，都通过新建buffer来展示其提供的内容。出于习惯，使用&lt;code&gt;q&lt;/code&gt;退出文件，当时vim上还有许多插件的buffer未退出，需要额外的指令执行退出(qa)。怎么在退出文件时，同时退出其它buffer？本文以vim初学者的，编写了一段vimscript实现该功能。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;经过调教的vim，肯定有很多插件，比如nerdtree，python-mode等等，很多插件在使用过程中，都通过新建buffer来展示其提供的内容。出于习惯，使用&lt;code&gt;q&lt;/code&gt;退出文件，当时vim上还有许多插件的buffer未退出，需要额外的指令执行退出(qa)。怎么在退出文件时，同时退出其它buffer？本文以vim初学者的，编写了一段vimscript实现该功能。&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2 id="_1"&gt;问题背景&lt;/h2&gt;
&lt;p&gt;经过一段时间的&lt;code&gt;vim&lt;/code&gt; 调教，写代码时，&lt;code&gt;vim&lt;/code&gt; 经常是下面的界面：&lt;/p&gt;
&lt;p&gt;&lt;img alt="vim window" src="https://xnow.me/usr/uploads/2022/04/671362123.png" /&gt;&lt;/p&gt;
&lt;p&gt;整个窗口分为好几块，分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左上：nerdtree 文件目录列表窗口&lt;/li&gt;
&lt;li&gt;下方：代码运行结果窗口&lt;/li&gt;
&lt;li&gt;左上：代码编辑窗口&lt;/li&gt;
&lt;li&gt;左中：代码检查窗口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了代码文件编辑窗口，其它窗口都是起辅助作用，因此，当退出代码编辑之后，应该要自动关闭其它窗口。&lt;/p&gt;
&lt;h2 id="_2"&gt;代码实现&lt;/h2&gt;
&lt;p&gt;为了实现这个功能，研究了下vimscript，使用如下代码，基本实现了该需求。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;autocmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WinEnter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;CloseBuffersIfAllFileClosed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;CloseBuffersIfAllFileClosed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;winbufs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;winnr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getbufinfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffertype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getbufvar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bufnr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;amp;buftype&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ERROR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffertype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;endif&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;endfor&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;qa&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;endif&lt;/span&gt;
&lt;span class="n"&gt;endfunction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这段代码逻辑很简单，很像伪代码，功能就是判断buffer的状态，如果没有打开文件的buffer，就执行&lt;code&gt;qa&lt;/code&gt;退出所有。&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;getbufinfo()&lt;/code&gt;函数获取所有的buffer信息，遍历所有的buffer，并获取其类型和&lt;code&gt;loaded&lt;/code&gt;状态。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果buffer类型是&lt;code&gt;""&lt;/code&gt; 空字符串，表示buffer对应的是正常文件&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;loaded&lt;/code&gt;状态为1，则表明文件已加载。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据这两个状态识别所有的buffer，从而判断是否有文件在打开，如果没有打开的文件，则vim自动执行&lt;code&gt;qa&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;将这段代码加入到 &lt;code&gt;~/.vimrc&lt;/code&gt;中，重新打开文件编辑即可生效。&lt;/p&gt;
&lt;h2 id="_3"&gt;其它相关&lt;/h2&gt;
&lt;p&gt;vim中其他和buffer相关的基础命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;vim中使用 &lt;code&gt;buffers&lt;/code&gt; 命令也可以在vim中获得&lt;code&gt;buffer&lt;/code&gt;列表，使用&lt;code&gt;buffers!&lt;/code&gt;，还可以额外获得&lt;code&gt;unloaded&lt;/code&gt;状态的buffer情况。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vim中使用&lt;code&gt;help buftype&lt;/code&gt;，可以看到所有buffer可能的状态，如&lt;code&gt;nofile&lt;/code&gt;、&lt;code&gt;help&lt;/code&gt;、&lt;code&gt;quickfix&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;p&gt;vimscript 基础语法：http://blog.chalda.cz/2018/08/07/Vim-script-notes.html&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/programs/vimscript-autoclose-buffers.html</guid>
      <pubDate>Fri, 08 Apr 2022 23:37:00 +0800</pubDate>
    </item>
    <item>
      <title>一句话crontab实现防ssh暴力破解</title>
      <link>http://xnow.me/linuxes/crontab-ssh-hosts-deny.html</link>
      <description>&lt;p&gt;将vps在公网上，难免会有居心叵测的人扫描，及时换了非标准的端口，依旧难以避免。因此编写一条Bash命令，放在crontab里，发现坏人立刻封掉ip。&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;将vps在公网上，难免会有居心叵测的人扫描，及时换了非标准的端口，依旧难以避免。因此编写一条Bash命令，放在crontab里，发现坏人立刻封掉ip。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;一句话命令如下：&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;crontab&lt;span class="w"&gt; &lt;/span&gt;-l
*&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt;  &lt;/span&gt;journalctl&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;ssh.service&lt;span class="w"&gt; &lt;/span&gt;--since&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;24 hours ago&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/Failed password/{if(NF&amp;gt;15){ips[$13]++}else{ips[$11]++}}END{for(ip in ips){if(ips[ip]&amp;gt;3){print ip}}}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ip&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;/etc/hosts.deny&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ALL: &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/hosts.deny&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt;24小时内输错5次ssh密码的客户端ip，将立刻被添加到 &lt;code&gt;/etc/hosts.deny&lt;/code&gt; 文件里做黑名单处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单解析：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该条语句可以按三个管道符号进行分割，下面分别讲解。&lt;/p&gt;
&lt;p&gt;管道的第一部分：使用 &lt;code&gt;journalctl&lt;/code&gt; 命令查询最近24小时内的登录日志，该命令适用于使用systemd的系统，如果非systemd，可以换成从 &lt;code&gt;/var/log/secure&lt;/code&gt; 文件过滤日志。&lt;/p&gt;
&lt;p&gt;管道的第二部分：主要是&lt;code&gt;awk&lt;/code&gt;过滤登录失败日志，关键字&lt;code&gt;Failed password&lt;/code&gt;。对登录失败的ip地址进行统计，输出失败次数大于3次的ip地址。此处用到了awk的数组功能做统计。&lt;/p&gt;
&lt;p&gt;管道的第三部分：去重，避免重复添加IP到&lt;code&gt;/etc/hosts.deny&lt;/code&gt;文件中。使用&lt;code&gt;while&lt;/code&gt;语句判断ip地址是否已经在 /etc/hosts.deny中，如果不在，则使用tee命令进行追加。&lt;/p&gt;
&lt;p&gt;该命令不仅实用，而且还很有趣。个人以为十分有学习价值。&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">http://xnow.me/linuxes/crontab-ssh-hosts-deny.html</guid>
      <pubDate>Fri, 26 Nov 2021 21:55:00 +0800</pubDate>
    </item>
  </channel>
</rss>
