<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>RanXian的博客</title><description>RanXian的博客，记录个人学习、工作、AI相关的内容</description><link>https://www.hellorx.site/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>V2.7.0</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年10月25日 00:01:30</lastBuildDate><item><title>大学生必修：焚决</title><link>https://www.hellorx.site/posts/others/others-university-competition/</link><guid isPermaLink="true">https://www.hellorx.site/posts/others/others-university-competition/</guid><description>大学竞赛的生存与修炼之道（焚决篇）。</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 免责声明
本文将站在纯专科学生的角度，探讨专科最该学的“功法”——竞赛！</p>
</blockquote>
<h2>一、认清现实：专科生天生断一条腿</h2>
<p>从未来想做研发、技术岗的角度来看，专科学生可以说天生断一条腿：你的专科学历在社会上面临着很多学历歧视，很多公司看到你的学历就会直接 pass。但是！——没有但是，我想说的是，即使你有专科竞赛经历，很多公司也不会因此高看你一眼。</p>
<p>因为这些比赛不一定有公司知道（除了那种需要大投入的比赛，例如 RM）；而且就算知道了又能怎样？换到老板的位置想一想：是要 985/211，还是专科生？很明显，985/211 的试错成本更低。</p>
<p>更何况，<strong>竞赛经历并不能证明你技术有多好，只能证明你花了时间。</strong></p>
<h2>二、那竞赛到底还搞不搞？</h2>
<p>那就有人要问了：学长学长，你这样说不就是没有搞竞赛的必要了？<strong>有！非常有！</strong></p>
<p>原因很简单：在专科里面，没什么比竞赛更有用的事情了。比赛虽然不能证明你有技术，但可以<strong>逼你</strong>去学技术。至于为什么？我就懒得说那么多了，总之就是——竞赛能让你学东西。那如果你连竞赛都不乐意学，怎么办？那只能说明你这个人<strong>不乐意学习</strong>，那我这边建议——<strong>电子厂一位</strong>。</p>
<h2>三、肘击犹豫：做回自己</h2>
<p>那我这里就又要肘击一下理由特别多的人了：“我不会怎么办”“这个比赛的东西我不想学怎么办”“比赛浪费时间”……那我只能说，这种人<strong>你给我滚好吗，你给我爬到宿舍楼顶跳下去好吗</strong>。</p>
<p>但是学长这种温文尔雅的人还是要多一点耐心的，所以，这里就对以上理由做出回答：</p>
<ol>
<li><strong>不会</strong>：竞赛的题目都是从简单到难的，你不会就学；你如果不愿意开始，那你永远都不会。</li>
<li><strong>不想学</strong>：竞赛的东西都是有实际应用的，你不想学就别学，但你既然选择了竞赛，那就得学。</li>
<li><strong>浪费时间</strong>：竞赛的时间都是利用课余时间，你如果觉得浪费时间，那只能说明你这个人<strong>不乐意学习</strong>，那我这边建议——<strong>找个楼跳一下</strong>。</li>
</ol>
<p>还有一种情况：比如这个比赛的老师找你，但这个比赛的技术你不感兴趣，你想学别的比赛的技术。那我建议还是加入现在的竞赛，因为老师要你是很难的——今天老师要你，明天可能就不要你了。比赛选人大概率不看技术，主要看一个学习态度，所以还是加入现在的竞赛，并且课余时间去学你感兴趣的技术。</p>
<h2>四、怎么加入竞赛</h2>
<p>这一方面我要先说明，每个学校和每个学校的情况都不一样，我只能以我中专+大专到现在 5 年竞赛经历来给出建议。</p>
<p>要我说，就俩字：<strong>装乖</strong>。说得不好听，对吗？我举几个理由论证：</p>
<ol>
<li>竞赛一般是指导老师 + 学长学姐双评判体系，大部分学校都是以学长学姐的建议为主。学长学姐如果足够有实力，那他们的话就不止有分量了——他们有足够的实力把你的水平拉高。站在学长学姐的角度考虑，肯定喜欢听话的学生，因为听话才好教。</li>
<li>老师的话就更不用说了：老师一般都有自己的项目，或者说有很多杂事，需要学生来完成。如果学生不听话，那老师就只能自己做，所以老师也喜欢听话的学生。</li>
</ol>
<p>那就有人要问了：“学长学长，这不是还要干杂活，和你之前说的学生会之类没区别？”那我就要问了：我之前怎么说的？我说<strong>入党入团进组织干杂活是值得的</strong>，那站在学技术的角度，进比赛备赛机房干杂活，就值得。</p>
<p>所以，<strong>要点装乖</strong>：听从指导老师和学长学姐的安排，完成他们交给你的任务。那就有人又会问：<strong>这不是跪着要饭了？</strong> 此言差矣——其实是蹲着的。而且竞赛机房的杂活基本都是技术类打杂，不是无意义打杂。</p>
]]></content:encoded></item><item><title>大学生必修：组织扩展篇</title><link>https://www.hellorx.site/posts/others/others-university-organization-expansion/</link><guid isPermaLink="true">https://www.hellorx.site/posts/others/others-university-organization-expansion/</guid><description>大学里的组织生存指南：学生会、社团那些事（组织扩展篇）。</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>所谓扩展篇，就是指大学内的各种组织——学生会、社团……</strong></p>
</blockquote>
<p>在我看来，校内各级组织以及校内社团，需要具体问题具体分析。</p>
<h2>校内组织</h2>
<h3>1. 学生会 / 团委</h3>
<p>不管在哪所学校，像学生会、团委这种校内组织（有些学校还会设立别的），我都喜欢统称为“党政机关单位”。它的本质作用其实很简单：学校没有足够的人手去管理学生，所以让学生管理学生——说白了，就是学校的一种管理手段。</p>
<p>我的建议自然是<strong>能不进就不进</strong>。别和我说什么锻炼自己、培养管理能力——我说白了，以后去私企，简历上写个“管理过学生会”就真的得烧高香了。</p>
<blockquote>
<p><strong>以上举例，想入党人员除外。</strong></p>
</blockquote>
<p>在我看来，进入所有的校/院级组织，无非就是为了更好入党。我不想入党，所以以上仅为技术工程师思路的建议。</p>
<h3>入党</h3>
<p>说到入党，我得把这事儿掰开了讲。如果说大学里有什么事务性工作是值得考虑的，那可能就是入党了——但前提是，你得做好心理准备：本质上就是要帮老师干各种杂事、给学校干活，以此来提高入党的概率。但这并不能保证你一定能够入党，因为名额有限，竞争激烈。而且你要付出的代价不小——无论是时间成本还是精力成本，都非常高昂。</p>
<p>这件事要不要做，完全看个人规划。如果你的家庭背景能够支持你找到一个好单位，而那个单位又明确需要党员身份，那入党是有必要的。但如果你是普通学生，想靠党员身份在社会招聘中获得明显优势，<strong>恐怕会失望</strong>。</p>
<p>党员身份只有在吃公家饭时才有一定的作用，而且即便如此，你也需要通过国考、省考或事业单位考试——光有党员身份是不够的。至于互联网大厂或普通的民营企业，党员身份<strong>几乎没有帮助</strong>。你花了那么多时间和精力去入党，换来的只是在处理琐事和文档方面变得更熟练，而你的专业技能并没有得到实质性提升。从这个角度看，<strong>投入产出比实在太低了</strong>。尤其是对于想走技术路线的同学来说，这些事务性工作反而会成为你个人成长的阻碍，因为它们会大量占用你本该用于学习和钻研的时间。</p>
<p>所以我的建议是：<strong>想清楚自己要什么，再决定要不要入。</strong> 别为了一个身份，把自己三年时间全搭进去。</p>
<h2>社团</h2>
<p>社团和组织就不一样了。在我看来，是否加入社团主要看两点：</p>
<ol>
<li><strong>你是否对这个社团感兴趣</strong></li>
<li><strong>这个社团是否能教真东西</strong></li>
</ol>
<p>我以一个 ICT 类社团举例：如果社团里有大佬级的学长学姐，而且你感兴趣，那就无脑进——别害怕不要你，害怕就给我学！</p>
<p>在这里我宣传一下自己的社团</p>
<p><img src="https://www.hellorx.site/_astro/others-university-organization-expansion-20260907-doggo-control.BZst_oZY_AH39L.webp" alt="宇树机器狗 Doggo2 控制界面截图，展示实时环境感知、3D 地图建模、动作指令与急停安全功能" loading="lazy" /></p>
<p>机器狗不多言</p>
<p>注意：<strong>社团一定不要为了学分进。</strong> 你来读大学不是来浪费时间的，不要为了那三分两分，把自己的时间全搭进去。</p>
<blockquote>
<p><strong>以上观点仅为技术工种路线。</strong></p>
</blockquote>
]]></content:encoded></item><item><title>Linux离线源办法</title><link>https://www.hellorx.site/posts/projects/projects-linux-offline-source/</link><guid isPermaLink="true">https://www.hellorx.site/posts/projects/projects-linux-offline-source/</guid><description>介绍 Linux 离线环境下的软件源搭建与使用方案。</description><pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 导读
本文应委托需求，特出一条离线环境下的软件源仓库配置办法，覆盖本地镜像源与内网全量镜像源两种场景。</p>
</blockquote>
<h2>一、Linux本地源</h2>
<h3>1. YUM源</h3>
<p>挂载安装光盘，并将 yum 源指向本地镜像，避免依赖外网：</p>
<pre><code class="language-bash">mkdir /media/cdrom

mount /dev/cdrom /media/cdrom/

cd /etc/yum.repos.d/
ls

# 备份默认源（不同系统本地源名字不一样）
mv kylin_x86_64.repo kylin_x86_64.repo.bak

# 新建本地源
vi local.repo
</code></pre>
<p><code>local.repo</code> 内容如下：</p>
<pre><code class="language-ini">[iso]
baseurl=file:///media/cdrom
enabled=1
gpgcheck=0
</code></pre>
<h3>2. APT源</h3>
<p>挂载 ISO 镜像，并将 apt 源指向本地镜像，避免依赖外网：</p>
<pre><code class="language-bash">sudo mkdir -p /mnt/iso

sudo mount -t iso9660 -o loop /opt/ubuntu-22.04.5-live-server-amd64.iso /mnt/iso

# 检查挂载情况
df -hT

# 备份默认源
sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak

# 新建本地源
sudo vi /etc/apt/sources.list
</code></pre>
<p><code>sources.list</code> 内容如下（<code>jammy</code> 为 ubuntu22.04 的版本代号）：</p>
<pre><code class="language-text">deb file:///mnt/iso jammy main restricted
</code></pre>
<blockquote>
<p>版本代号对照：<code>xenial</code>（16.04）、<code>bionic</code>（18.04）、<code>focal</code>（20.04）、<code>jammy</code>（22.04），请按实际系统版本替换。</p>
</blockquote>
<p>检查验证：</p>
<pre><code class="language-bash">sudo apt-get update

sudo apt-get install curl vim ssh
</code></pre>
<h2>二、指定源仓库下载</h2>
<p>这个办法指在部分内网环境通过一台服务器获取全量源，然后通过内网给其他服务器使用。</p>
<h3>1. YUM源</h3>
<p>在一台能访问外网的服务器上，通过 reposync 将网络源的全量 rpm 包下载到 httpd 默认发布目录，内网其他服务器将 yum 源指向该服务器：</p>
<pre><code class="language-bash"># 备份默认源，从阿里云下载 yum 配置文件
cd /etc/yum.repos.d/ &amp;&amp; mkdir backup &amp;&amp; mv *.repo backup/

curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

# 安装相关软件包（yum-utils 提供 reposync）
yum -y install createrepo yum-utils httpd

# 创建仓库目录（httpd 默认发布目录，无需额外配置）
mkdir -p /var/www/html/yumrepos

# 根据 CentOS-Base.repo 同步阿里云软件包到本地
reposync -p /var/www/html/yumrepos/

# 生成仓库元数据（为 rpm 包建立依赖索引，客户端才能解析依赖）
createrepo /var/www/html/yumrepos/base

# 启动 httpd
systemctl enable httpd &amp;&amp; systemctl start httpd
</code></pre>
<blockquote>
<p>下载完成后将服务器接入内网（或通过双网卡使内网可达）。若仓库有增量更新，重新执行 <code>reposync -p /var/www/html/yumrepos/</code>，再执行 <code>createrepo --update /var/www/html/yumrepos/base</code> 增量生成元数据。</p>
</blockquote>
<p>内网其他服务器配置源：</p>
<pre><code class="language-bash">vi /etc/yum.repos.d/local.repo
</code></pre>
<p><code>local.repo</code> 内容如下（<code>server_ip</code> 替换为YUM服务器内网地址）：</p>
<pre><code class="language-ini">[base]
name=Local Base
baseurl=http://server_ip/yumrepos/base
enabled=1
gpgcheck=0
</code></pre>
<p>检查验证：</p>
<pre><code class="language-bash">yum clean all

yum makecache

yum repolist
</code></pre>
<h3>2. APT源</h3>
<p>在一台能访问外网的服务器上通过 apt-mirror 同步全量 deb 包，借助 apache2 提供 HTTP 服务，内网其他服务器将 apt 源指向该服务器：</p>
<pre><code class="language-bash"># 下载机安装 apt-mirror 与 apache2
apt-get install -y apt-mirror apache2

# 编辑镜像列表
vi /etc/apt/mirror.list
</code></pre>
<p><code>mirror.list</code> 内容如下：</p>
<pre><code>set base_path /var/spool/apt-mirror
set nthreads 20

deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
clean http://mirrors.aliyun.com/ubuntu/
</code></pre>
<p>同步全量源并发布：</p>
<pre><code class="language-bash"># 执行同步（约几十 G，支持断点续传，中断后重新执行即可）
apt-mirror

# 若中断后无法启动，删除锁文件后重试
sudo rm -f /var/spool/apt-mirror/var/apt-mirror.lock

# 软链接镜像目录到 apache2 默认发布目录
sudo ln -s /var/spool/apt-mirror/mirror/mirrors.aliyun.com/ubuntu /var/www/html/ubuntu

# 启动 apache2
sudo systemctl enable --now apache2
</code></pre>
<p>内网其他服务器配置源：</p>
<pre><code class="language-bash">sudo vi /etc/apt/sources.list
</code></pre>
<p><code>sources.list</code> 内容如下（<code>server_ip</code> 替换为下载机内网地址）：</p>
<pre><code class="language-text">deb [arch=amd64] http://server_ip/ubuntu/ jammy main restricted universe multiverse
</code></pre>
<p>检查验证：</p>
<pre><code class="language-bash">sudo apt-get update

sudo apt-get install curl vim ssh
</code></pre>
<h2>三、总结</h2>
<ul>
<li><strong>本地源</strong>：适合无外网并且小规模的离线环境，直接挂载 ISO 镜像或光盘即可，常见于比赛环境。</li>
<li><strong>指定源仓库下载</strong>：适合生产环境，通过一台服务器同步全量源，其他服务器通过 HTTP 访问，可以根据需求扩展软件包。</li>
<li><strong>注意事项</strong>：<ul>
<li>本地源无需额外服务，但需提前准备 ISO 镜像或光盘。</li>
<li>指定源仓库下载需部署 HTTP 服务，但可支持增量更新。</li>
<li>挂载 ISO 镜像时注意版本代号（如 <code>jammy</code> 为 Ubuntu 22.04），否则软件包无法识别。</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>大学生必修：炼气篇</title><link>https://www.hellorx.site/posts/others/others-university-lessons-basics/</link><guid isPermaLink="true">https://www.hellorx.site/posts/others/others-university-lessons-basics/</guid><description>大学四年的少走弯路经验分享（炼气篇）。</description><pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>“大学要么拼命卷天卷地学三年，要么醉生梦死混三年。”</strong>
—— 一位工程师好友说给我的话</p>
</blockquote>
<h2>一、打破滤镜的大学真相</h2>
<p>借用B站麒麟安全枷锁老师视频里的一句话：<strong>“现在的大学本质上更像商业化教育机构。”</strong> 站在 ICT 专业学生的角度考虑，现在的大学很难学到有用的专业知识。时代一直在变，3 年前没有人会知道如今的 AI 能到今天这个地步，然而目前大学里教授的内容，基本是上个时代的“遗老遗少”——课件是过期的技术、教学方式是固定模板，学校内教导的知识已经严重与社会脱节。</p>
<h2>二、为什么大学学不到东西</h2>
<ol>
<li>大学的教师都是一路升到研究生的学术路线，真正有技术的工程师基本不会去大学当老师。<strong>大学本质是一个无论年纪的舒适圈</strong>，或者说成为老师之后就不需要提升技术了，大学的老师更倾向于研究学术，而不是考虑一线实战。但是不可否认的一点是，所有大学老师基本都是令我望而却步的理论高手。</li>
</ol>
<h2>三、专科 &gt; 本科</h2>
<p><strong>在两边一样自律的情况下本科生远超专科生</strong>，但是我从一个专科生，以及混迹竞赛圈多年的角度来看：从技术方面上看，专科生的技术水平普遍比本科选手好，所以网上就会笑谈“本升专”。而社会上普遍要本不要专，可是以专科学生的观点来看，如果一名专科学生技术大牛，那么他的文化就不行，从而学历就被卡住了。不要和我说技术厉害会被大厂特招——这种万中无一的事情，我晚上把枕头垫到天花板都梦不出。在我看来，如果真的想从事 ICT 行业的工作，就是——奉献：一没关系、二没人脉，那就是<strong>一手抓技术、一手抓文化</strong>，还想打游戏？你打个蛋！</p>
<h2>四、你到底要做什么</h2>
<p>用我大学数学老师的一段话：<strong>“大学这三年，你总要做点什么吧。”</strong> 是不是听不懂？我比如说打游戏：我李某人不是瞧不起打游戏，而是打游戏它可以是娱乐，也可以从娱乐转成职业，但是不能是无意义的，不能是浪费时间的，我同班一个同学就可以靠比赛赚钱。我的意思是：<strong>“你到底要做什么？”</strong> 选一条路走下去，不要摇摆不定，不要浪费时间。不要和我说什么“万一没成功呢”“没成功只能进厂了”——你如果连自己到底想做什么都没有想清楚，选了一条路还摇摇摆摆，那么你只能进厂了。所以我的建议是——<strong>不如进厂包吃住</strong>。</p>
<h2>五、你给我跳出来</h2>
<p>如果你有路了，就不要嘴上说“我想专升本”“我想做 XX 工程师”，然后继续混了——这种话谁不会说？我小时候还想修仙呢。<strong>你爬起来，爬起来还不够</strong>：不要天天按部就班地上课、打卡、考试，不要一下课就觉得是休息时间——没几年可以休息了。光爬起来还不够，<strong>你还要去跑、去跳、去做一个漂亮的倒挂金钩</strong>。平时上完课去学点东西，看看能不能混进竞赛实训室，看看能不能找个学长带带路、少走点弯路。</p>
<h2>六、最后</h2>
<p>闭上眼把手放在你的心口问问<strong>你究竟想做什么，真正想要什么</strong>。睁开眼找到那条路走下去，<strong>去跑去跳去探索去试错去犯错</strong>你没几年了。</p>
]]></content:encoded></item><item><title>Kylin进阶教程</title><link>https://www.hellorx.site/posts/projects/projects-itai-kylin-20250001/</link><guid isPermaLink="true">https://www.hellorx.site/posts/projects/projects-itai-kylin-20250001/</guid><description>基于银河麒麟Server V10的运维环境实战</description><pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 导读
本文根据银河麒麟Server V10运维真实环境配置教程，内容涵盖Docker、Mariadb 主备、Harbor、GlusterFS、Tomcat、Haproxy、rsyslog、Ansible、PostgreSQL</p>
</blockquote>
<h2>一、server1 系统环境配置</h2>
<h3>1、修改主机名</h3>
<pre><code class="language-bash">hostnamectl set-hostname server1.kylin.com
bash
</code></pre>
<h3>2、创建多级目录并查看</h3>
<pre><code class="language-bash">mkdir -p ~/kylin/docs/note
find ~/kylin -type d
</code></pre>
<h3>3、复制文件并改名</h3>
<pre><code class="language-bash">cp /etc/passwd ~/passwd_backup
</code></pre>
<h3>4、查看文件权限并修改为 640</h3>
<pre><code class="language-bash">ls -l /root/passwd_backup
chmod 640 /root/passwd_backup
</code></pre>
<h3>5、配置 YUM 仓库源</h3>
<pre><code class="language-bash">cd /etc/yum.repos.d/
mv kylin_x86_64.repo kylin_x86_64.repo.bak
vim local.repo
</code></pre>
<pre><code class="language-ini">[local]
name=Local Kylin Repo
baseurl=http://192.168.122.1:58000/kylinsrv
enabled=1
gpgcheck=0
</code></pre>
<pre><code class="language-bash">yum clean all
yum makecache
</code></pre>
<h3>6、安装配置 vsftpd</h3>
<pre><code class="language-bash">yum install -y vsftpd
vim /etc/vsftpd/vsftpd.conf
</code></pre>
<pre><code class="language-ini">anonymous_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_root=/data/ftp/
local_enable=YES
write_enable=YES
listen=YES
listen_ipv6=NO

# 受限用户 chroot
chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list
allow_writeable_chroot=YES

# 双份日志
xferlog_enable=YES
xferlog_file=/var/log/xferlog
dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd.log
</code></pre>
<p>创建 ftp 用户并限制主目录：</p>
<pre><code class="language-bash">useradd -s /sbin/nologin -d /home/kylinftp kylinftp
passwd kylinftp
touch /etc/vsftpd/chroot_list
</code></pre>
<blockquote>
<p>将 <code>/etc/pam.d/vsftpd</code> 中的 <code>auth required pam_shells.so</code> 一行注释掉，避免 <code>nologin</code> 用户无法登录。</p>
</blockquote>
<pre><code class="language-bash">systemctl enable --now vsftpd
systemctl restart vsftpd
</code></pre>
<h3>7、配置 chrony 时间服务器</h3>
<pre><code class="language-bash">yum install -y chrony
vim /etc/chrony.conf
</code></pre>
<pre><code class="language-ini">server 192.168.122.1 iburst
allow 172.16.0.0/16
</code></pre>
<pre><code class="language-bash">systemctl restart chronyd
chronyc sources
</code></pre>
<h3>8、配置 DNS 正向解析</h3>
<pre><code class="language-bash">yum install -y bind
sed -i '/listen-on port 53/s/127.0.0.1/any/' /etc/named.conf
sed -i '/allow-query/s/localhost/any/' /etc/named.conf
cat &gt;&gt; /etc/named.rfc1912.zones &lt;&lt;EOF
zone "kylin.com" IN {
	type master;
	file "kylin.com.zone";
};
EOF
</code></pre>
<pre><code class="language-bash">cat &gt; /var/named/kylin.com.zone &lt;&lt; 'EOF'
$TTL 1D
@	IN SOA	server1.kylin.com. rname.invalid. (
					2025010101	; serial
					1D	; refresh
					1H	; retry
					1W	; expire
					3H )	; minimum
	NS	server1.kylin.com.
server1	A	172.16.50.231
server2	A	172.16.50.232
EOF
chown named:named /var/named/kylin.com.zone
systemctl enable --now named
systemctl restart named
nslookup server1.kylin.com 127.0.0.1
nslookup server2.kylin.com 127.0.0.1
</code></pre>
<h3>9、配置 NFS 网络共享</h3>
<pre><code class="language-bash">yum install -y nfs-utils
mkdir -p /nfsdata/KY01 /nfsdata/KY02
cat &gt; /etc/exports &lt;&lt;EOF
/nfsdata/KY01 172.16.50.0/24(rw,sync,no_root_squash)
/nfsdata/KY02 server2.kylin.com(ro,sync,no_root_squash)
EOF
systemctl enable --now rpcbind nfs-server
exportfs -r
showmount -e localhost
</code></pre>
<h2>二、server1 Docker 容器集群管理</h2>
<h3>1、二进制方式部署 Docker 20.10.9</h3>
<pre><code class="language-bash">cd /opt/software/
tar xf docker-20.10.9.tgz
cp docker/* /usr/bin/
mkdir -p /etc/docker
vim /etc/docker/daemon.json
</code></pre>
<pre><code class="language-json">{
  "data-root": "/var/lib/docker",
  "storage-driver": "overlay2"
}
</code></pre>
<pre><code class="language-bash">cat &gt; /usr/lib/systemd/system/docker.service &lt;&lt;'EOF'
[Unit]
Description=Docker Application Container Engine
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now docker
docker version
</code></pre>
<h3>2、导入镜像并创建容器</h3>
<pre><code class="language-bash">docker load -i /opt/software/kylin-10-sp3.tar.xz
docker images

docker run -itd --name kylin-1 --cpus=1 -m 2g kylin:laster /bin/bash
docker run -itd --name kylin-2 --cpus=1 -m 2g kylin:laster /bin/bash
docker ps
</code></pre>
<h3>3、容器内安装配置 Mariadb</h3>
<p>将安装包拷入容器并进入容器安装：</p>
<pre><code class="language-bash">docker cp /opt/software/mariadb-*.tar.gz kylin-1:/tmp/
docker cp /opt/software/mariadb-*.tar.gz kylin-2:/tmp/
docker exec -it kylin-1 bash
</code></pre>
<p>容器内（kylin-1、kylin-2 均执行）：</p>
<pre><code class="language-bash">useradd -M -s /sbin/nologin mysql
mkdir -p /opt/mariadb /data/mysql /var/log/mysql
tar xf /tmp/mariadb-*.tar.gz -C /opt/mariadb --strip-components=1
chown -R mysql:mysql /data/mysql /var/log/mysql
/opt/mariadb/bin/mysqld --initialize-insecure --user=mysql --datadir=/data/mysql
/opt/mariadb/bin/mysqld_safe --user=mysql --datadir=/data/mysql &amp;
</code></pre>
<p>登录并设置 root 密码、开放网段授权：</p>
<pre><code class="language-sql">/opt/mariadb/bin/mysql -uroot
ALTER USER 'root'@'localhost' IDENTIFIED BY 'you_passwd';
CREATE USER 'root'@'%' IDENTIFIED BY 'you_passwd';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.16.%' IDENTIFIED BY 'you_passwd';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.17.%' IDENTIFIED BY 'you_passwd';
FLUSH PRIVILEGES;
</code></pre>
<h3>4、Mariadb 互为主备</h3>
<p>修改 <code>/etc/my.cnf</code> 配置（kylin-1 上）：</p>
<pre><code class="language-ini">[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = row
auto_increment_increment = 2
auto_increment_offset = 1
</code></pre>
<p>kylin-2 上 <code>server-id = 2</code>、<code>auto_increment_offset = 2</code>。</p>
<p>重启两容器数据库：</p>
<pre><code class="language-bash">/opt/mariadb/bin/mysqladmin -uroot -pyou_passwd shutdown
/opt/mariadb/bin/mysqld_safe --user=mysql --datadir=/data/mysql &amp;
</code></pre>
<p>两容器均创建备份用户：</p>
<pre><code class="language-sql">/opt/mariadb/bin/mysql -uroot -pyou_passwd
CREATE USER 'backup'@'%' IDENTIFIED BY 'you_passwd';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'backup'@'%';
FLUSH PRIVILEGES;
</code></pre>
<p>kylin-1 上指向 kylin-2：</p>
<pre><code class="language-sql">CHANGE MASTER TO MASTER_HOST='kylin-2', MASTER_USER='backup', MASTER_PASSWORD='you_passwd', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=4;
START SLAVE;
</code></pre>
<p>kylin-2 上指向 kylin-1：</p>
<pre><code class="language-sql">CHANGE MASTER TO MASTER_HOST='kylin-1', MASTER_USER='backup', MASTER_PASSWORD='you_passwd', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=4;
START SLAVE;
</code></pre>
<p>验证：</p>
<pre><code class="language-sql">SHOW SLAVE STATUS\G;
</code></pre>
<h3>5、创建数据库与表</h3>
<pre><code class="language-sql">/opt/mariadb/bin/mysql -uroot -pyou_passwd
CREATE DATABASE kylin_db1;
USE kylin_db1;
CREATE TABLE student_info (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    Name VARCHAR(20) NOT NULL,
    Birthday DATE NOT NULL,
    Gender ENUM('male','female','x') NOT NULL DEFAULT 'x',
    Password VARCHAR(100) NOT NULL
);
INSERT INTO student_info (Name, Birthday, Gender, Password) VALUES
('tom',  '1990-04-12', 'male',   PASSWORD('you_passwd')),
('harry', '1993-07-05', 'female', PASSWORD('you_passwd')),
('barry', '1992-02-23', 'male',   PASSWORD('you_passwd'));
SELECT * FROM student_info;
</code></pre>
<h2>三、server1 Harbor 镜像仓库</h2>
<h3>1、docker-compose 离线安装 Harbor</h3>
<pre><code class="language-bash">cp /opt/software/docker-compose /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose version

cd /opt/software/
tar zxf harbor-offline-installer-*.gz
cd harbor
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
</code></pre>
<pre><code class="language-yaml">hostname: 172.16.50.231
http:
  port: 8000
harbor_admin_password: you_passwd
database:
  password: you_passwd
data_volume: /data/harbor
</code></pre>
<pre><code class="language-bash">./install.sh
docker-compose ps
</code></pre>
<h3>2、API 创建用户、项目并添加成员</h3>
<pre><code class="language-bash">curl -u "admin:you_passwd" -X POST \
"http://172.16.50.231:8000/api/v2.0/users" \
-H "Content-Type: application/json" \
-d '{"username":"kylin","email":"kylin@yourdomain.com","password":"you_passwd","realname":"Kylin User","comment":"User created via API"}'

curl -u "admin:you_passwd" -X POST \
"http://172.16.50.231:8000/api/v2.0/projects" \
-H "Content-Type: application/json" \
-d '{"project_name":"kylin_web","metadata":{"public":"false"}}'

curl -u "admin:you_passwd" -X GET \
"http://172.16.50.231:8000/api/v2.0/users?username=kylin"

curl -u "admin:you_passwd" -X POST \
"http://172.16.50.231:8000/api/v2.0/projects/kylin_web/members" \
-H "Content-Type: application/json" \
-d '{"role_id": 4, "member_user": {"username": "kylin", "user_id": 5}}'
</code></pre>
<h3>3、上传镜像到 Harbor</h3>
<pre><code class="language-bash">docker login 172.16.50.231:8000 -u kylin -p you_passwd
docker tag kylin:laster 172.16.50.231:8000/kylin_web/kylin:laster
docker push 172.16.50.231:8000/kylin_web/kylin:laster
</code></pre>
<h2>四、GlusterFS 存储集群（两台主机执行）</h2>
<pre><code class="language-bash">yum install -y glusterfs-server
systemctl enable --now glusterd
</code></pre>
<h3>1、格式化磁盘并挂载（两台）</h3>
<pre><code class="language-bash">mkfs.xfs /dev/vdb
mkfs.xfs /dev/vdc
mkdir -p /data/storage/bricks01 /data/storage/bricks02
mkdir -p /data/storage/bricks01/safe_pool /data/storage/bricks02/data_pool
echo "/dev/vdb /data/storage/bricks01 xfs defaults 0 0" &gt;&gt; /etc/fstab
echo "/dev/vdc /data/storage/bricks02 xfs defaults 0 0" &gt;&gt; /etc/fstab
mount -a
df -h
</code></pre>
<h3>2、添加信任存储节点</h3>
<pre><code class="language-bash">gluster peer probe server2.kylin.com
gluster peer status
</code></pre>
<h3>3、创建复制卷 gv_safe</h3>
<pre><code class="language-bash">gluster volume create gv_safe replica 2 \
server1.kylin.com:/data/storage/bricks01/safe_pool \
server2.kylin.com:/data/storage/bricks01/safe_pool
</code></pre>
<h3>4、创建分布式卷 gv_data</h3>
<pre><code class="language-bash">gluster volume create gv_data \
server1.kylin.com:/data/storage/bricks02/data_pool \
server2.kylin.com:/data/storage/bricks02/data_pool
</code></pre>
<h3>5、启动卷并设置访问控制</h3>
<pre><code class="language-bash">gluster volume start gv_safe
gluster volume start gv_data
gluster volume set gv_safe auth.allow 172.16.50.0/24
gluster volume info
</code></pre>
<h2>五、server1 Java 应用环境配置</h2>
<h3>1、部署 Java 与 Tomcat</h3>
<pre><code class="language-bash">mkdir -p /opt/java /opt/tomcat1 /opt/tomcat2
tar xf /opt/software/java-23.tar.gz -C /opt/java --strip-components=1
tar xf /opt/software/apache-tomcat-10.1.94.tar.gz -C /opt/tomcat1 --strip-components=1
cp -r /opt/tomcat1/* /opt/tomcat2/
</code></pre>
<h3>2、创建 tomcat 用户</h3>
<pre><code class="language-bash">groupadd -g 1080 tomcat
useradd -u 1080 -g tomcat -s /sbin/nologin -M tomcat
chown -R tomcat:tomcat /opt/java /opt/tomcat1 /opt/tomcat2
</code></pre>
<h3>3、配置 Java 环境变量</h3>
<pre><code class="language-bash">cat &gt;&gt; /home/tomcat/.bash_profile &lt;&lt;EOF

export JAVA_HOME=/opt/java
export CLASSPATH=/opt/java/lib
export PATH=$PATH:/opt/java/bin
EOF
</code></pre>
<h3>4、配置两套 Tomcat 端口</h3>
<pre><code class="language-bash">sed -i 's/port="8080"/port="8081"/' /opt/tomcat1/conf/server.xml
sed -i 's/port="8005"/port="8006"/' /opt/tomcat1/conf/server.xml
sed -i 's/port="8009"/port="8010"/' /opt/tomcat1/conf/server.xml

sed -i 's/port="8080"/port="8082"/' /opt/tomcat2/conf/server.xml
sed -i 's/port="8005"/port="8007"/' /opt/tomcat2/conf/server.xml
sed -i 's/port="8009"/port="8011"/' /opt/tomcat2/conf/server.xml
</code></pre>
<h3>5、配置默认首页</h3>
<pre><code class="language-bash">echo "helloJava_ps_1" &gt; /opt/tomcat1/webapps/ROOT/index.jsp
echo "helloJava_ps_2" &gt; /opt/tomcat2/webapps/ROOT/index.jsp
</code></pre>
<h3>6、tomcat.service 统一管理两套实例</h3>
<pre><code class="language-bash">cat &gt; /etc/systemd/system/tomcat.service &lt;&lt;'EOF'
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking
User=tomcat
Group=tomcat
Environment=JAVA_HOME=/opt/java
ExecStart=/bin/bash -c '/opt/tomcat1/bin/startup.sh &amp;&amp; /opt/tomcat2/bin/startup.sh'
ExecStop=/bin/bash -c '/opt/tomcat1/bin/shutdown.sh &amp;&amp; /opt/tomcat2/bin/shutdown.sh'

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tomcat
systemctl start tomcat
systemctl stop tomcat
</code></pre>
<p>验证：</p>
<pre><code class="language-bash">curl http://172.16.50.231:8081/
curl http://172.16.50.231:8082/
</code></pre>
<h2>六、server1 配置负载均衡 Haproxy</h2>
<pre><code class="language-bash">rpm -ivh /opt/software/haproxy-*.rpm
vim /etc/haproxy/haproxy.cfg
</code></pre>
<pre><code class="language-ini">global
    maxconn 4096

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http_front
    bind *:8088
    default_backend java_servers

backend java_servers
    balance roundrobin
    server tomcat1 127.0.0.1:8081 check
    server tomcat2 127.0.0.1:8082 check

listen stats
    bind *:8009
    mode http
    stats enable
    stats uri /haproxy?stats
</code></pre>
<pre><code class="language-bash">systemctl enable --now haproxy
systemctl restart haproxy
curl http://172.16.50.231:8088/
curl "http://172.16.50.231:8009/haproxy?stats"
</code></pre>
<h2>七、server1 日志管理</h2>
<h3>1、Tomcat 日志统一存放并修改前缀</h3>
<pre><code class="language-bash">mkdir -p /var/log/tomcat/logs
chown tomcat:tomcat /var/log/tomcat/logs

sed -i 's|CATALINA_OUT="$CATALINA_BASE"/logs/catalina.out|CATALINA_OUT=/var/log/tomcat/logs/tomcat_1-catalina.out|' /opt/tomcat1/bin/catalina.sh
sed -i 's|^1catalina.org.apache.juli.AsyncFileHandler.prefix=catalina.|1catalina.org.apache.juli.AsyncFileHandler.prefix=tomcat_1-catalina.|' /opt/tomcat1/conf/logging.properties

sed -i 's|CATALINA_OUT="$CATALINA_BASE"/logs/catalina.out|CATALINA_OUT=/var/log/tomcat/logs/tomcat_2-catalina.out|' /opt/tomcat2/bin/catalina.sh
sed -i 's|^1catalina.org.apache.juli.AsyncFileHandler.prefix=catalina.|1catalina.org.apache.juli.AsyncFileHandler.prefix=tomcat_2-catalina.|' /opt/tomcat2/conf/logging.properties

systemctl restart tomcat
ls /var/log/tomcat/logs/
</code></pre>
<h3>2、rsyslog 发送日志到 server2</h3>
<pre><code class="language-bash">cat &gt; /etc/rsyslog.d/10-forwards.conf &lt;&lt;EOF
*.* @@172.16.50.232:514
EOF
systemctl restart rsyslog
</code></pre>
<h2>八、server2 系统环境配置</h2>
<h3>1、修改主机名</h3>
<pre><code class="language-bash">hostnamectl set-hostname server2.kylin.com
bash
</code></pre>
<h3>2、配置 YUM 源（ftp）</h3>
<pre><code class="language-bash">cat &gt; /etc/yum.repos.d/ftp.repo &lt;&lt;EOF
[ftp]
name=Server1 FTP Repo
baseurl=ftp://server1.kylin.com
enabled=1
gpgcheck=0
EOF
yum clean all
yum makecache
</code></pre>
<h3>3、用户组管理与密码策略</h3>
<pre><code class="language-bash">groupadd devgroup
useradd -G devgroup dev1
useradd -G devgroup dev2
passwd dev1
cat &gt; /etc/sudoers.d/devgroup &lt;&lt;EOF
%devgroup ALL=(ALL) NOPASSWD: ALL
EOF
chage -M 90 -W 7 -m 2 dev1
chage -l dev1
</code></pre>
<h3>4、目录 SGID 特殊权限</h3>
<pre><code class="language-bash">mkdir /opt/project
chown root:devgroup /opt/project
chmod 2775 /opt/project
</code></pre>
<h3>5、创建 pk.sh 监控脚本</h3>
<pre><code class="language-bash">cat &gt; /opt/pk.sh &lt;&lt;'EOF'
#!/bin/bash
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed 's/.*, *\([0-9.]*\)%* id.*/\1/' | awk '{print 100-$1}')
MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
MEM_USED=$(free -m | awk '/^Mem:/{print $3}')
MEM_PERCENT=$(awk -v u="$MEM_USED" -v t="$MEM_TOTAL" 'BEGIN{printf "%.1f", u/t*100}')
echo "CPU Usage: ${CPU_USAGE}%"
echo "Memory Usage: ${MEM_USED}/${MEM_TOTAL}MB(${MEM_PERCENT}%)"
EOF
chmod 750 /opt/pk.sh
/opt/pk.sh | tee /var/log/monitor.log
</code></pre>
<h3>6、创建 info.sh 参数脚本</h3>
<pre><code class="language-bash">cat &gt; /opt/info.sh &lt;&lt;'EOF'
#!/bin/bash
if [ "$1" = "kylin" ] || [ "$1" = "ITAI" ]; then
    echo "$1"
else
    echo "Error 99" &gt;&amp;2
fi
EOF
chmod 750 /opt/info.sh
/opt/info.sh kylin
/opt/info.sh ITAI
/opt/info.sh xx
</code></pre>
<h3>7、配置 chrony 时间同步</h3>
<pre><code class="language-bash">yum install -y chrony
sed -i '/^pool /d' /etc/chrony.conf
echo "server server1.kylin.com iburst" &gt;&gt; /etc/chrony.conf
systemctl restart chronyd
chronyc sources
</code></pre>
<h3>8、指定 DNS 服务器</h3>
<pre><code class="language-bash">echo "nameserver 172.16.50.231" &gt; /etc/resolv.conf
nslookup server1.kylin.com
nslookup server2.kylin.com
</code></pre>
<h3>9、网络共享</h3>
<pre><code class="language-bash">yum install -y nfs-utils autofs
mkdir -p /nfs/net_public /nfsdata/net_dir
</code></pre>
<p>KY01 使用 autofs 挂载：</p>
<pre><code class="language-bash">cat &gt;&gt; /etc/auto.master &lt;&lt;EOF
/nfs/net_public /etc/auto.misc --timeout=60
EOF
cat &gt;&gt; /etc/auto.misc &lt;&lt;EOF
KY01 -fstype=nfs,rw server1.kylin.com:/nfsdata/KY01
EOF
systemctl enable --now autofs
systemctl restart autofs
ls /nfs/net_public/KY01
df -h
</code></pre>
<p>KY02 开机自动挂载：</p>
<pre><code class="language-bash">echo "server1.kylin.com:/nfsdata/KY02 /nfsdata/net_dir nfs ro,_netdev 0 0" &gt;&gt; /etc/fstab
mount -a
df -h
</code></pre>
<h2>九、server2 Ansible 自动化部署 nginx</h2>
<h3>1、安装 ansible 并配置清单</h3>
<pre><code class="language-bash">rpm -ivh /opt/software/ansible-*.rpm
mkdir -p /etc/ansible
cat &gt; /etc/ansible/hosts &lt;&lt;EOF
[web]
server1.kylin.com
EOF
ssh-keygen -t rsa -N ""
ssh-copy-id root@server1.kylin.com
</code></pre>
<h3>2、创建 nginx role</h3>
<pre><code class="language-bash">mkdir -p /etc/ansible/roles/nginx/{tasks,templates,files}
cp /opt/software/nginx.conf /etc/ansible/roles/nginx/files/
cp /opt/software/nginx.j2 /etc/ansible/roles/nginx/templates/
cp /opt/software/nginx-*.rpm /etc/ansible/roles/nginx/files/nginx.rpm
</code></pre>
<pre><code class="language-bash">cat &gt; /etc/ansible/roles/nginx/tasks/main.yml &lt;&lt;'EOF'
---
- name: 拷贝 nginx 软件包
  copy: src=nginx.rpm dest=/opt/software/nginx.rpm

- name: 安装 nginx
  yum: name=/opt/software/nginx.rpm state=present

- name: 渲染 nginx 配置模板
  template: src=nginx.j2 dest=/etc/nginx/nginx.conf
  notify: restart nginx

- name: 启动 nginx
  service: name=nginx state=started enabled=yes
EOF
</code></pre>
<pre><code class="language-bash">cat &gt; /etc/ansible/roles/nginx/handlers/main.yml &lt;&lt;'EOF'
---
- name: restart nginx
  service: name=nginx state=restarted
EOF
</code></pre>
<h3>3、编写 playbook 并执行</h3>
<pre><code class="language-bash">cat &gt; /etc/ansible/nginx.yml &lt;&lt;'EOF'
---
- hosts: web
  roles:
    - nginx
EOF
ansible-playbook /etc/ansible/nginx.yml
ansible web -m ping
curl http://server1.kylin.com/
</code></pre>
<h2>十、server2 PostgreSQL 数据库运维</h2>
<h3>1、安装初始化 postgresql</h3>
<pre><code class="language-bash">rpm -ivh /opt/software/postgresql-server-*.rpm
/usr/bin/postgresql-setup --initdb
systemctl enable --now postgresql
</code></pre>
<pre><code class="language-bash">vim /var/lib/pgsql/data/postgresql.conf
</code></pre>
<pre><code class="language-ini">listen_addresses = '*'
port = 5432
</code></pre>
<pre><code class="language-bash">cat &gt;&gt; /var/lib/pgsql/data/pg_hba.conf &lt;&lt;EOF
host    all    all    172.16.50.0/24    md5
EOF
systemctl restart postgresql
</code></pre>
<p>设置 postgres 口令：</p>
<pre><code class="language-bash">su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'you_passwd';\""
</code></pre>
<h3>2、创建数据库并导入</h3>
<pre><code class="language-bash">su - postgres -c "createdb company_db"
su - postgres -c "psql company_db &lt; /opt/software/company_db.sql"
</code></pre>
<h3>3、数据库操作（按顺序执行）</h3>
<p>查询每部门员工数量、平均薪资、最高薪资，按平均薪资降序：</p>
<pre><code class="language-bash">su - postgres -c "psql company_db -c \"SELECT d.department_name, COUNT(e.employee_id) AS emp_count, AVG(e.salary) AS avg_salary, MAX(e.salary) AS max_salary FROM employees e JOIN departments d ON e.department_id = d.department_id GROUP BY d.department_name ORDER BY avg_salary DESC;\"" &gt; /data/result_1.log
</code></pre>
<p>查询参与“ERP系统升级”项目的员工信息：</p>
<pre><code class="language-bash">su - postgres -c "psql company_db -c \"SELECT e.employee_name, d.department_name, p.role, p.work_hours FROM employees e JOIN departments d ON e.department_id = d.department_id JOIN project_members p ON e.employee_id = p.employee_id JOIN projects pr ON p.project_id = pr.project_id WHERE pr.project_name = 'ERP系统升级';\"" &gt; /data/result_2.log
</code></pre>
<p>查询薪资高于部门平均薪资的员工：</p>
<pre><code class="language-bash">su - postgres -c "psql company_db -c \"SELECT e.employee_name, d.department_name, e.salary, a.avg_salary FROM employees e JOIN departments d ON e.department_id = d.department_id JOIN (SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id) a ON e.department_id = a.department_id WHERE e.salary &gt; a.avg_salary;\"" &gt; /data/result_3.log
</code></pre>
<p>技术部（部门 ID 为 1）薪资低于 12000 的员工加薪 10%，最高不超过 13000：</p>
<pre><code class="language-bash">su - postgres -c "psql company_db -c \"UPDATE employees SET salary = LEAST(salary * 1.1, 13000) WHERE department_id = 1 AND salary &lt; 12000;\""
</code></pre>
<blockquote>
<p>表名/字段名以 <code>company_db.sql</code> 实际结构为准，如不一致按实际结构调整 SQL。</p>
</blockquote>
<h3>4、挂载 gv_safe 并进行完整备份</h3>
<pre><code class="language-bash">yum install -y glusterfs-client
mkdir -p /mnt/backup
echo "server1.kylin.com:/gv_safe /mnt/backup glusterfs defaults,_netdev 0 0" &gt;&gt; /etc/fstab
mount -a
df -h

su - postgres -c "pg_dump company_db" &gt; /mnt/backup/db_backup.sql
ls -l /mnt/backup/
</code></pre>
<h3>5、配置 crontab 定期备份</h3>
<pre><code class="language-bash">mkdir -p /data/backup /data/script
cat &gt; /data/script/backup.sh &lt;&lt;'EOF'
#!/bin/bash
su - postgres -c "pg_dump company_db" &gt; /data/backup/db_backup_$(date +%F).sql
EOF
chmod +x /data/script/backup.sh

cat &gt; /data/script/store.sh &lt;&lt;'EOF'
#!/bin/bash
tar czf /mnt/backup/db_backup_$(date +%F).tar.gz -C /data/backup db_backup_*.sql
EOF
chmod +x /data/script/store.sh

crontab -e
</code></pre>
<pre><code class="language-cron">55 23 * * 0 bash /data/script/backup.sh
0 23 25 * * bash /data/script/store.sh
</code></pre>
<pre><code class="language-bash">crontab -l
</code></pre>
<h2>十一、server2 rsyslog 日志服务器</h2>
<pre><code class="language-bash">vim /etc/rsyslog.conf
</code></pre>
<p>取消以下注释：</p>
<pre><code class="language-ini">$ModLoad imudp
$UDPServerRun 514
$ModLoad imtcp
$InputTCPServerRun 514
</code></pre>
<p>创建日志收集模板：</p>
<pre><code class="language-bash">cat &gt; /etc/rsyslog.d/20-logs-template.conf &lt;&lt;'EOF'
$template logs,"/var/log/remote/%HOSTNAME%/%programname%.log"
*.* ?logs
EOF
systemctl restart rsyslog
</code></pre>
<p>验证：</p>
<pre><code class="language-bash">ss -tunlp | grep 514
ls /var/log/remote/
ls /var/log/remote/server1/
</code></pre>
]]></content:encoded></item><item><title>openEuler信创适配与安全管理教程</title><link>https://www.hellorx.site/posts/projects/projects-openeuler-xinchuang-adaptation/</link><guid isPermaLink="true">https://www.hellorx.site/posts/projects/projects-openeuler-xinchuang-adaptation/</guid><description>基于openEuler的信创运维环境实战</description><pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 导读
本文根据openEuler信创运维真实环境配置教程，内容涵盖系统安装、网口绑定、DNS、CA证书、Redis集群、Apache、WordPress、NFS、openGauss、安全加固</p>
</blockquote>
<h2>一、信创系统安装（Server3 openEuler）</h2>
<p>为 Server3 安装 openEuler 操作系统，放入安装介质进入安装界面，依次完成：</p>
<ul>
<li>系统语言：English</li>
<li>系统时区：Asia/Shanghai</li>
<li>安装类型：Server</li>
<li>root 密码：启用（口令为 <code>you_passwd</code>），加密方式选择 SM3</li>
<li>磁盘分区：选择自定义分区，使用 LVM 逻辑卷</li>
</ul>
<table>
<thead>
<tr>
<th>位置</th>
<th>容量</th>
<th>文件系统</th>
<th>分区类型</th>
</tr>
</thead>
<tbody><tr>
<td>/（根分区）</td>
<td>25 GiB</td>
<td>ext4</td>
<td>LVM</td>
</tr>
<tr>
<td>swap</td>
<td>4 GiB</td>
<td>swap</td>
<td>LVM</td>
</tr>
<tr>
<td>/opt</td>
<td>5 GiB</td>
<td>xfs</td>
<td>LVM</td>
</tr>
</tbody></table>
<blockquote>
<p>系统启动分区（/boot）保持不变，其他未提及的配置均保持默认。安装完成后重启进入系统。</p>
</blockquote>
<h2>二、信创系统管理</h2>
<h3>1、网口绑定（Server3）</h3>
<p>使用 ens3 和 ens4 配置网口绑定，balance-alb 模式，连接名 <code>nm_bond1</code>，设备名 <code>bond1</code>：</p>
<pre><code class="language-bash">nmcli con add type bond con-name nm_bond1 ifname bond1 mode balance-alb
nmcli con add type ethernet con-name bond1_port1 ifname ens3 master bond1
nmcli con add type ethernet con-name bond1_port2 ifname ens4 master bond1

nmcli con mod nm_bond1 ipv4.method manual ipv4.addresses 172.16.50.103/24
nmcli con mod nm_bond1 ipv4.gateway 172.16.50.1
nmcli con mod nm_bond1 ipv6.addresses 2402:2025::103/64 ipv6.method manual

nmcli con up nm_bond1
nmcli dev status
cat /proc/net/bonding/bond1
</code></pre>
<h3>2、网络地址与名称配置（所有主机）</h3>
<p>各主机 IP 与 FQDN 规划如下：</p>
<table>
<thead>
<tr>
<th>服务器</th>
<th>FQDN</th>
<th>IPv4</th>
<th>IPv6</th>
</tr>
</thead>
<tbody><tr>
<td>Server1</td>
<td>node1.server.itai.org</td>
<td>ens3:172.16.50.101/24，ens4:10.0.0.101/24</td>
<td>ens3:2402:2025::101/64</td>
</tr>
<tr>
<td>Server2</td>
<td>node2.server.itai.org</td>
<td>ens3:172.16.50.102/24，ens4:10.0.0.102/24</td>
<td>ens3:2402:2025::102/64</td>
</tr>
<tr>
<td>Server3</td>
<td>node3.server.itai.org</td>
<td>bond1:172.16.50.103/24</td>
<td>bond1:2402:2025::103/64</td>
</tr>
<tr>
<td>Desktop</td>
<td>ws.server.itai.org</td>
<td>eth0:172.16.50.111/24</td>
<td>eth0:2402:2025::111/64</td>
</tr>
</tbody></table>
<p>以 Server1 为例，配置主机名与地址：</p>
<pre><code class="language-bash">hostnamectl set-hostname node1.server.itai.org
bash

nmcli con mod ens3 ipv4.method manual ipv4.addresses 172.16.50.101/24 ipv4.gateway 172.16.50.1
nmcli con mod ens3 ipv6.addresses 2402:2025::101/64 ipv6.method manual
nmcli con mod ens4 ipv4.method manual ipv4.addresses 10.0.0.101/24
nmcli con up ens3 ens4
</code></pre>
<p>Server2、Desktop 按上表参数同理配置。所有主机间访问均使用 FQDN。</p>
<p>在 <code>/etc/hosts</code> 中补充解析兜底（DNS 搭建完成前使用）：</p>
<pre><code class="language-bash">cat &gt;&gt; /etc/hosts &lt;&lt;EOF
172.16.50.101 node1.server.itai.org
172.16.50.102 node2.server.itai.org
172.16.50.103 node3.server.itai.org
172.16.50.111 ws.server.itai.org
EOF
</code></pre>
<h3>3、网关、防火墙与 SSH 免密</h3>
<p>确认所有 Server 主机网关为 172.16.50.1，并校验连通：</p>
<pre><code class="language-bash">ping -c 4 172.16.50.1
</code></pre>
<p>启用防火墙，区域为 public，按服务放行端口（永久生效，以 Server1 为例）：</p>
<pre><code class="language-bash">systemctl enable --now firewalld
firewall-cmd --set-default-zone=public

firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-port=53/tcp --add-port=53/udp
firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp
firewall-cmd --permanent --add-port=18901-18903/tcp --add-port=28901-28903/tcp
firewall-cmd --permanent --add-port=5432/tcp
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload
firewall-cmd --list-all
</code></pre>
<blockquote>
<p>放行端口需与实际部署服务对应：DNS 53、Redis 18901-18903（含集群总线 +10000）、Apache 80/443、openGauss 5432、MySQL 3306、NFS 服务另放行 2049/111/rpc-bind。</p>
</blockquote>
<p>设置 root 密码并确保可通过 SSH 远程登录（所有 Server）：</p>
<pre><code class="language-bash">echo 'you_passwd' | passwd --stdin root
grep -E 'PasswordAuthentication|PermitRootLogin' /etc/ssh/sshd_config
echo "PasswordAuthentication yes" &gt;&gt; /etc/ssh/sshd_config
systemctl restart sshd
</code></pre>
<p>生成 2 组 SSH 密钥对（RSA 4096 与国密 SM2），名称和位置保持默认：</p>
<pre><code class="language-bash">ssh-keygen -t rsa -b 4096 -N ""
ssh-keygen -t sm2 -N ""
ls -l ~/.ssh/
</code></pre>
<p>利用 FQDN 配置 Server 主机间 SSH 免密登录（每台主机均执行，将本机公钥分发到其他主机）：</p>
<pre><code class="language-bash">ssh-copy-id node1.server.itai.org
ssh-copy-id node2.server.itai.org
ssh-copy-id node3.server.itai.org

ssh node2.server.itai.org 'hostname'
</code></pre>
<h3>4、DNS 主备服务（Server1 主、Server2 备）</h3>
<p>安装 bind：</p>
<pre><code class="language-bash">yum install -y bind bind-utils
</code></pre>
<p><strong>Server1（主 DNS）</strong>：修改 <code>/etc/named.conf</code>，监听任意地址、允许任意网段查询：</p>
<pre><code class="language-bash">sed -i 's/listen-on port 53 { 127.0.0.1; };/listen-on port 53 { any; };/' /etc/named.conf
sed -i 's/listen-on-v6 port 53 { ::1; };/listen-on-v6 port 53 { any; };/' /etc/named.conf
sed -i 's/allow-query     { localhost; };/allow-query     { any; };/' /etc/named.conf
</code></pre>
<p>添加正反向区域：</p>
<pre><code class="language-bash">cat &gt;&gt; /etc/named.rfc1912.zones &lt;&lt;EOF
zone "server.itai.org" IN {
	type master;
	file "db.server.itai.org";
};

zone "0.50.16.172.in-addr.arpa" IN {
	type master;
	file "db.0.50.16.172";
};
EOF
</code></pre>
<p>正向解析文件 <code>/var/named/db.server.itai.org</code>：</p>
<pre><code class="language-bash">cat &gt; /var/named/db.server.itai.org &lt;&lt; 'EOF'
$TTL 1D
@	IN SOA	node1.server.itai.org. root.server.itai.org. (
					2025010101	; serial
					1D	; refresh
					1H	; retry
					1W	; expire
					3H )	; minimum
	NS	node1.server.itai.org.
	NS	node2.server.itai.org.
node1	A	172.16.50.101
node2	A	172.16.50.102
node3	A	172.16.50.103
ws	A	172.16.50.111
www	A	172.16.50.101
blog	A	172.16.50.102
EOF
</code></pre>
<p>反向解析文件 <code>/var/named/db.0.50.16.172</code>：</p>
<pre><code class="language-bash">cat &gt; /var/named/db.0.50.16.172 &lt;&lt; 'EOF'
$TTL 1D
@	IN SOA	node1.server.itai.org. root.server.itai.org. (
					2025010101	; serial
					1D	; refresh
					1H	; retry
					1W	; expire
					3H )	; minimum
	NS	node1.server.itai.org.
	NS	node2.server.itai.org.
101	PTR	node1.server.itai.org.
102	PTR	node2.server.itai.org.
103	PTR	node3.server.itai.org.
111	PTR	ws.server.itai.org.
EOF
chown root:named /var/named/db.server.itai.org /var/named/db.0.50.16.172
chmod 640 /var/named/db.server.itai.org /var/named/db.0.50.16.172
</code></pre>
<p><strong>Server2（备用 DNS）</strong>：配置为 slave，从主服务器同步区域：</p>
<pre><code class="language-bash">cat &gt;&gt; /etc/named.rfc1912.zones &lt;&lt;EOF
zone "server.itai.org" IN {
	type slave;
	masters { 172.16.50.101; };
	file "slaves/db.server.itai.org";
};

zone "0.50.16.172.in-addr.arpa" IN {
	type slave;
	masters { 172.16.50.101; };
	file "slaves/db.0.50.16.172";
};
EOF
</code></pre>
<p>启动并验证（两台均执行）：</p>
<pre><code class="language-bash">systemctl enable --now named
rndc reload
nslookup node1.server.itai.org 127.0.0.1
nslookup node2.server.itai.org 172.16.50.102
nslookup 172.16.50.103
</code></pre>
<p>所有主机的 <code>/etc/resolv.conf</code> 指向 DNS 服务器：</p>
<pre><code class="language-bash">cat &gt; /etc/resolv.conf &lt;&lt;EOF
search server.itai.org
nameserver 172.16.50.101
nameserver 172.16.50.102
EOF
</code></pre>
<h3>5、CA 证书服务（Server1）</h3>
<p>初始化 CA 目录结构与数据库：</p>
<pre><code class="language-bash">mkdir -p /etc/pki/CA/{private,newcerts,certs,crl}
touch /etc/pki/CA/index.txt
echo 1000 &gt; /etc/pki/CA/serial
chmod 700 /etc/pki/CA/private
</code></pre>
<p>修改 <code>/etc/pki/tls/openssl.cnf</code> 中 <code>[ CA_default ]</code> 段与 <code>[ policy_match ]</code> 策略：检查国家和组织两个字段是否匹配，其他字段可选，公用名必填：</p>
<pre><code class="language-ini">[ CA_default ]
dir		= /etc/pki/CA
certs		= $dir/certs
crl_dir		= $dir/crl
database	= $dir/index.txt
new_certs_dir	= $dir/newcerts
certificate	= $dir/cacert.pem
serial		= $dir/serial
private_key	= $dir/private/cakey.pem
default_days	= 365
default_md	= sha256
policy		= policy_match

[ policy_match ]
countryName		= match
stateOrProvinceName	= optional
organizationName	= match
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional
</code></pre>
<p>生成 CA 私钥与自签名证书（RSA 4096，有效期 1 年，详细信息如下）：</p>
<pre><code class="language-bash">openssl genrsa -out /etc/pki/CA/private/cakey.pem 4096
openssl req -new -x509 -key /etc/pki/CA/private/cakey.pem \
  -out /etc/pki/CA/cacert.pem -days 365 \
  -subj "/CN=ca-rsa.itai.org/C=CN/ST=Beijing/L=Beijing/O=itai.org/OU=ca-rsa"
openssl x509 -in /etc/pki/CA/cacert.pem -noout -text
</code></pre>
<p>为 Server 主机签发证书（CN 为泛域名，有效期 2 年，RSA 4096）：</p>
<pre><code class="language-bash">openssl genrsa -out /etc/pki/CA/private/server-rsa.key 4096
openssl req -new -key /etc/pki/CA/private/server-rsa.key \
  -out /etc/pki/CA/server-rsa.csr \
  -subj "/CN=*.itai.org/C=CN/ST=shanghai/L=shanghai/O=itai.org/OU=server"

openssl ca -in /etc/pki/CA/server-rsa.csr \
  -out /etc/pki/CA/certs/server-rsa.pem -days 730 \
  -notext -batch

openssl x509 -in /etc/pki/CA/certs/server-rsa.pem -noout -text
</code></pre>
<p>将主机证书复制到所有 Server 主机的 <code>/etc/ssl</code> 目录：</p>
<pre><code class="language-bash">scp /etc/pki/CA/certs/server-rsa.pem node2.server.itai.org:/etc/ssl/
scp /etc/pki/CA/certs/server-rsa.pem node3.server.itai.org:/etc/ssl/
scp /etc/pki/CA/private/server-rsa.key node2.server.itai.org:/etc/ssl/
scp /etc/pki/CA/private/server-rsa.key node3.server.itai.org:/etc/ssl/
</code></pre>
<blockquote>
<p>后续服务需要使用普通证书的均使用此证书 server-rsa.pem / server-rsa.key。</p>
</blockquote>
<h3>6、Redis 集群（Server1 + Server2）</h3>
<p>安装 redis 并为每个节点单独准备配置文件：</p>
<pre><code class="language-bash">yum install -y redis
mkdir -p /opt/redis-cluster/node1/data /opt/redis-cluster/node2/data /opt/redis-cluster/node3/data
mkdir -p /opt/redis-cluster/node4/data /opt/redis-cluster/node5/data /opt/redis-cluster/node6/data
</code></pre>
<p>集群节点分布如下：</p>
<table>
<thead>
<tr>
<th>Redis Node_Name</th>
<th>Server_Name/Port</th>
</tr>
</thead>
<tbody><tr>
<td>node1</td>
<td>172.16.50.101:18901</td>
</tr>
<tr>
<td>node2</td>
<td>172.16.50.101:18902</td>
</tr>
<tr>
<td>node3</td>
<td>172.16.50.101:18903</td>
</tr>
<tr>
<td>node4</td>
<td>172.16.50.102:18901</td>
</tr>
<tr>
<td>node5</td>
<td>172.16.50.102:18902</td>
</tr>
<tr>
<td>node6</td>
<td>172.16.50.102:18903</td>
</tr>
</tbody></table>
<p>各节点独立配置文件 <code>/opt/redis-cluster/[node_name].conf</code>，以 node1 为例：</p>
<pre><code class="language-ini">bind 172.16.50.101
port 18901
daemonize yes
pidfile /var/run/redis_18901.pid
logfile /var/log/redis_18901.log
dir /opt/redis-cluster/node1/data
appendonly yes
cluster-enabled yes
cluster-config-file nodes-node1.conf
cluster-node-timeout 5000
</code></pre>
<blockquote>
<p>node2、node3 在 Server1 上（端口 18902/18903，dir 指向 node2、node3 的 data 目录）；node4、node5、node6 在 Server2 上（端口 18901/18902/18903，bind 172.16.50.102）。</p>
</blockquote>
<p>启动全部节点并创建集群（3 主 3 从，每个主节点 1 个从节点）：</p>
<pre><code class="language-bash">redis-server /opt/redis-cluster/node1.conf
redis-server /opt/redis-cluster/node2.conf
redis-server /opt/redis-cluster/node3.conf

redis-server /opt/redis-cluster/node4.conf
redis-server /opt/redis-cluster/node5.conf
redis-server /opt/redis-cluster/node6.conf

redis-cli --cluster create \
172.16.50.101:18901 172.16.50.102:18901 172.16.50.101:18902 \
172.16.50.102:18902 172.16.50.101:18903 172.16.50.102:18903 \
--cluster-replicas 1
</code></pre>
<p>验证集群状态：</p>
<pre><code class="language-bash">redis-cli -c -p 18901 cluster info
redis-cli -c -p 18901 cluster nodes
</code></pre>
<h3>7、Apache 服务（Server1）</h3>
<p>安装 httpd 与 SSL 模块：</p>
<pre><code class="language-bash">yum install -y httpd mod_ssl curl-openssl
</code></pre>
<p>配置 <code>/etc/httpd/conf.d/itai.conf</code>：</p>
<pre><code class="language-apache"># itai.org 及三级域名访问时跳转到 www.itai.org
RewriteEngine on
RewriteCond %{HTTP_HOST} !^www\.itai\.org$ [NC]
RewriteCond %{HTTP_HOST} (^|\.)itai\.org$ [NC]
RewriteRule ^(.*)$ http://www.itai.org$1 [L,R=301]

# 使用 IP 地址访问时返回 403
&lt;VirtualHost 172.16.50.101:80&gt;
    ServerName 172.16.50.101
    &lt;Location /&gt;
        Require all denied
    &lt;/Location&gt;
&lt;/VirtualHost&gt;

# 域名访问默认页面
&lt;VirtualHost *:80&gt;
    ServerName www.itai.org
    DocumentRoot /var/www/html
&lt;/VirtualHost&gt;

# http 跳转 https
&lt;VirtualHost *:80&gt;
    ServerName www.itai.org
    Redirect permanent / https://www.itai.org/
&lt;/VirtualHost&gt;

&lt;VirtualHost *:443&gt;
    ServerName www.itai.org
    SSLEngine on
    SSLCertificateFile /etc/ssl/server-rsa.pem
    SSLCertificateKeyFile /etc/ssl/server-rsa.key
    DocumentRoot /var/www/html
&lt;/VirtualHost&gt;
</code></pre>
<p>写入默认页面并启动：</p>
<pre><code class="language-bash">echo "Hello_ITAI" &gt; /var/www/html/index.html
systemctl enable --now httpd
systemctl restart httpd
curl http://www.itai.org/
curl -I http://172.16.50.101/
</code></pre>
<h3>8、企业应用部署（Server2 LAMP + WordPress）</h3>
<p>安装 LAMP 组件：</p>
<pre><code class="language-bash">yum install -y httpd httpd-tools mariadb-server mariadb php php-fpm php-mysqlnd php-gd php-xml php-mbstring
systemctl enable --now httpd mariadb php-fpm
</code></pre>
<p>初始化数据库并设置 root 密码，创建 wordpress 库与数据库用户：</p>
<pre><code class="language-bash">mysql
ALTER USER 'root'@'localhost' IDENTIFIED BY 'you_passwd';
CREATE DATABASE wordpress;
CREATE USER 'wpadmin'@'localhost' IDENTIFIED BY 'you_passwd';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpadmin'@'localhost';
FLUSH PRIVILEGES;
EXIT;
</code></pre>
<p>部署 WordPress：</p>
<pre><code class="language-bash">tar xf /opt/software/wordpress-*.tar.gz -C /var/www/html/
cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php
sed -i "s/database_name_here/wordpress/" /var/www/html/wordpress/wp-config.php
sed -i "s/username_here/wpadmin/" /var/www/html/wordpress/wp-config.php
sed -i "s/password_here/you_passwd/" /var/www/html/wordpress/wp-config.php
</code></pre>
<p>配置 blog 虚拟主机：</p>
<pre><code class="language-bash">cat &gt; /etc/httpd/conf.d/blog.conf &lt;&lt;EOF
&lt;VirtualHost *:80&gt;
    ServerName blog.itai.org
    DocumentRoot /var/www/html/wordpress
    &lt;Directory /var/www/html/wordpress&gt;
        AllowOverride All
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
EOF
systemctl restart httpd
</code></pre>
<p>浏览器访问 <code>http://blog.itai.org</code> 完成安装，站点信息：</p>
<ul>
<li>站点标题：Technology Innovation @ CHINA</li>
<li>管理员名称：admin，密码：<code>you_passwd</code></li>
<li>管理员邮箱：<a href="mailto:mail@itai.org">mail@itai.org</a></li>
</ul>
<h3>9、NFS 文件存储（Server3）</h3>
<p>安装并创建共享目录，创建存储专用用户：</p>
<pre><code class="language-bash">yum install -y nfs-utils
mkdir -p /data/nfs/lun_clutser
groupadd -g 1111 nfsusers
useradd -u 1111 -g 1111 -s /sbin/nologin nfsusers
</code></pre>
<p>配置 exports，允许 node1、node2 以 FQDN 形式读写访问，数据落盘后才返回成功（sync），所有操作映射到 nfsusers：</p>
<pre><code class="language-bash">cat &gt; /etc/exports &lt;&lt;EOF
/data/nfs/lun_clutser node1.server.itai.org(rw,sync,no_wdelay,all_squash,anonuid=1111,anongid=1111)
/data/nfs/lun_clutser node2.server.itai.org(rw,sync,no_wdelay,all_squash,anonuid=1111,anongid=1111)
EOF
systemctl enable --now rpcbind nfs-server
exportfs -r
exportfs -v
</code></pre>
<p>防火墙放行 NFS 相关端口：</p>
<pre><code class="language-bash">firewall-cmd --permanent --add-service=nfs --add-service=rpc-bind --add-service=mountd
firewall-cmd --reload
</code></pre>
<p><strong>node1、node2 开机自动挂载</strong>，启用服务器超时检测（timeo=3 即 300ms），重试 3 次：</p>
<pre><code class="language-bash">yum install -y nfs-utils
mkdir -p /mnt/lun_clutser
echo "node3.server.itai.org:/data/nfs/lun_clutser /mnt/lun_clutser nfs soft,bg,timeo=3,retrans=3 0 0" &gt;&gt; /etc/fstab
mount -a
df -h /mnt/lun_clutser
touch /mnt/lun_clutser/test_write
ls -l /mnt/lun_clutser/
</code></pre>
<h2>三、信创系统适配验证</h2>
<h3>1、openGauss 部署与建库（Server3）</h3>
<p>通过 yum 仓库安装 openGauss 及配套工具：</p>
<pre><code class="language-bash">yum install -y opengauss
</code></pre>
<p>以 opengauss 系统用户初始化数据库并启动：</p>
<pre><code class="language-bash">su - opengauss
mkdir -p /var/lib/opengauss/data
gs_initdb -D /var/lib/opengauss/data --nodename=pg1 -E UTF8 --locale=C -w you_passwd
gs_ctl start -D /var/lib/opengauss/data
gsql -d postgres -p 5432
</code></pre>
<blockquote>
<p>openGauss 超级用户 opengauss 的口令为 <code>you_passwd</code>，该系统用户口令相同。</p>
</blockquote>
<p>创建数据库用户 oaadmin、实例 db_oa 及模式 sche_oa：</p>
<pre><code class="language-sql">CREATE USER oaadmin IDENTIFIED BY 'you_passwd';
CREATE DATABASE db_oa OWNER oaadmin;
CREATE SCHEMA sche_oa AUTHORIZATION oaadmin;
ALTER ROLE oaadmin IN DATABASE db_oa SET search_path TO sche_oa;
</code></pre>
<blockquote>
<p>题面所述 dbA 即实例 db_oa。oaadmin 是 sche_oa、db_oa 的拥有者，登录 db_oa 后默认查询模式为 sche_oa。</p>
</blockquote>
<p>在 sche_oa 中创建表 <code>tb_userinfo</code>：</p>
<pre><code class="language-sql">\c db_oa oaadmin
CREATE TABLE sche_oa.tb_userinfo (
    user_sk      INT4,
    c_user_id    CHAR(5),
    c_first_name CHAR(10),
    c_last_name  CHAR(10),
    c_role       CHAR(10),
    PRIMARY KEY (user_sk)
);
\d sche_oa.tb_userinfo
</code></pre>
<p>以纯文本形式将 db_oa 导出到 <code>/data/db_oa.sql</code>：</p>
<pre><code class="language-bash">su - opengauss -c "gs_dump -U oaadmin -W you_passwd -p 5432 db_oa -f /data/db_oa.sql"
head -20 /data/db_oa.sql
</code></pre>
<p>创建数据库用户 opsadmin、实例 db_wordpress 及模式 schema_wordpress：</p>
<pre><code class="language-sql">CREATE USER opsadmin IDENTIFIED BY 'you_passwd';
CREATE DATABASE db_wordpress OWNER opsadmin;
CREATE SCHEMA schema_wordpress AUTHORIZATION opsadmin;
ALTER ROLE opsadmin IN DATABASE db_wordpress SET search_path TO schema_wordpress;
</code></pre>
<h3>2、数据迁移与备份</h3>
<p>在 Desktop 上安装数据库管理工具 Navicat，完成 wordpress 数据迁移：</p>
<ol>
<li>新建 MySQL 连接（Server2，库 wordpress），记录源数据；</li>
<li>新建 openGauss 连接（Server3，库 db_wordpress）；</li>
<li>使用“数据传输”功能，将 Server2 的 wordpress 库完整迁移至 openGauss 的 db_wordpress 中（仅迁移数据）；</li>
<li>迁移完成后检查数据完整性、可用性。</li>
</ol>
<p>将迁移完成的数据库备份到 <code>/data/opengauss.sql</code>：</p>
<pre><code class="language-bash">su - opengauss -c "gs_dump -U opsadmin -W you_passwd -p 5432 db_wordpress -f /data/opengauss.sql"
ls -l /data/opengauss.sql
</code></pre>
<h2>四、信创系统安全加固</h2>
<h3>1、系统登录加固（所有系统）</h3>
<p>修改全局环境变量实现终端超时 180 秒自动登出：</p>
<pre><code class="language-bash">echo "export TMOUT=180" &gt;&gt; /etc/profile
source /etc/profile
</code></pre>
<p>删除不必要的用户 game 和 ftp：</p>
<pre><code class="language-bash">userdel -r game
userdel -r ftp
</code></pre>
<h3>2、SSH 服务加固（所有系统）</h3>
<p>在 <code>/etc/ssh/sshd_config</code> 中增加如下配置：</p>
<pre><code class="language-ini">ClientAliveInterval 20
ClientAliveCountMax 3
AllowTcpForwarding no
X11Forwarding no
MaxAuthTries 6
</code></pre>
<pre><code class="language-bash">systemctl restart sshd
ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub
</code></pre>
<h3>3、渗透测试（FLAG 取证）</h3>
<p>通过本地 PC 中的渗透测试平台对目标服务器场景进行渗透测试，各项操作结果作为 FLAG 提交：</p>
<ol>
<li><strong>系统版本扫描</strong>：对服务器场景进行版本扫描，将结果显示中系统信息的确切版本作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">nmap -sV -O 172.16.50.101
</code></pre>
<ol>
<li><strong>还原攻击路径</strong>：发现终端服务（远程登录服务）处于开启状态，还原黑客攻击路径，将服务端口号作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">nmap -sT -p- 172.16.50.101
</code></pre>
<ol>
<li><strong>恶意可执行文件</strong>：进入管理员桌面目录，查找遗留的恶意可执行文件，将文件全称作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">ls -la /root/Desktop/
file /root/Desktop/*
</code></pre>
<ol>
<li><strong>窃取的文本文档</strong>：在管理员桌面下查找窃取文本文档，将文件内容作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">cat /root/Desktop/*.txt
</code></pre>
<ol>
<li><strong>窃取的文档内容</strong>：在管理员用户的文档目录中查找窃取的文件，将文件内容作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">ls -la /root/Documents/
cat /root/Documents/*
</code></pre>
<ol>
<li><strong>后门用户</strong>：排查系统中的后门用户，将用户名作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">grep -v nologin /etc/passwd
awk -F: '$3 == 0 {print $1}' /etc/passwd
</code></pre>
<ol>
<li><strong>桌面背景</strong>：查找服务器场景中的桌面背景文件，将文件全称作为 FLAG 提交</li>
</ol>
<pre><code class="language-bash">find / -iname "*.jpg" -o -iname "*.png" -o -iname "*.bmp" 2&gt;/dev/null | grep -i wallpaper
ls -la /usr/share/backgrounds/
</code></pre>
<h2>五、材料汇报（PPT）</h2>
<p>以信创适配数据迁移师身份，制作一份政府门户网站迁移方案汇报 PPT，建议结构：</p>
<ol>
<li><strong>项目背景</strong>：说明从 Windows Server 迁移至国产信创服务器的必要性；</li>
<li><strong>现状分析</strong>：现有门户的软硬件架构、数据规模、依赖组件盘点；</li>
<li><strong>迁移方案</strong>：服务器选型（openEuler）、数据库迁移（MySQL → openGauss）、应用适配方案；</li>
<li><strong>实施步骤</strong>：分阶段迁移计划、数据迁移与校验、业务切换与回退预案；</li>
<li><strong>风险与保障</strong>：安全加固、备份策略、应急与回退方案、验收标准。</li>
</ol>
]]></content:encoded></item><item><title>使用DeepSeek Harness进行远程服务器运维</title><link>https://www.hellorx.site/posts/ai/ai-deepseek-harness-deployment/</link><guid isPermaLink="true">https://www.hellorx.site/posts/ai/ai-deepseek-harness-deployment/</guid><description>在云服务器上部署 DeepSeek Harness 并通过远程访问让其成为服务器运维助手的完整教程。</description><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<h1>探索未至之境：把 DeepSeek Harness 变成我的远程运维助手</h1>
<blockquote>
<p>[!NOTE] 提示
本文完整记录 DeepSeek Harness 在云服务器上的部署流程：环境准备与构建、systemd 服务化、Nginx 反代与 Basic Auth 访问认证、远程访问控制，以及三次典型的「公网可访问但接口不可用」排障。重点是 Host 信任（trustedHosts）、浏览器兼容性（randomUUID）与回环信任（loopback fence）的逐层打通，文中所有命令均亲测通过。</p>
</blockquote>
<hr />
<h2>一、背景与方案选择</h2>
<h3>1、为什么要在博客上部署 DeepSeek Harness</h3>
<p>2026 年 8 月 13 日，DeepSeek Harness 正式发布，正如它的宣传语所说——<strong>探索未至之境</strong>——上手一试，确实很强：Agent 会话、评测任务管理一应俱全，本地跑起来只能「自己用」，总感觉有点浪费。</p>
<p>于是我就琢磨开了：既然博客就搭在云服务器上，那能不能把 DeepSeek Harness 也部署上去，让它成为我的远程<strong>「运维助手」</strong>？人在哪儿，助手就在哪儿，浏览器一开就能用。说干就干，这么做的好处其实很实在：</p>
<ul>
<li>家里、公司、出差路上，只要有浏览器就能访问，不用背着开发机跑；</li>
<li>Agent 长任务（评测、批量跑实验）可以 7×24 小时挂在服务器上跑，不占本地资源；</li>
<li>多台设备共享同一个会话历史（<code>session.history</code>），工作记录不丢。</li>
</ul>
<h3>2、踩坑记录：三次典型排障</h3>
<blockquote>
<p>[!TIP] 建议
从「公网能打开页面」到「特权 API 全部可用」，一共踩了三个典型的坑。这三个坑的本质是<strong>一条信任链的逐层打通</strong>：第一层是 Host 信任（trustedHosts），第二层是浏览器兼容性（randomUUID），第三层是回环信任（loopback fence）。排障时按这个思路逐层排查，能少走很多弯路。每个坑按下述结构整理，方便复现排查。</p>
</blockquote>
<h3>问题① 外部可访问但 <code>/api</code> 返回 403 → 添加 <code>--trusted-host</code></h3>
<ul>
<li><p><strong>现象</strong>：公网访问 DeepSeek Harness Web UI 页面正常（HTML 加载正常），但页面里的 <code>/api</code> 相关请求全部返回 403，功能不可用。浏览器开发者工具（Network 标签）可见大量 <code>/api/*</code> 请求返回 403。</p>
</li>
<li><p><strong>根因</strong>：DeepSeek Harness 的 trustedHosts（Host 信任）机制会校验请求 Host。公网 Host 是 <code>服务器公网地址:对外端口</code>，不在默认信任列表里，非特权 API 方法被拒绝。</p>
</li>
<li><p><strong>修复</strong>：</p>
<ol>
<li><p>在 systemd 服务 <code>ExecStart</code> 中追加两个 <code>--trusted-host</code>（带端口与不带端口双形态）：</p>
<pre><code class="language-sh">dsh web --trusted-host 服务器公网地址:对外端口 --trusted-host 服务器公网地址
</code></pre>
</li>
<li><p>同时 <code>location /</code> 里必须用 <code>proxy_set_header Host $http_host;</code> <strong>保留端口</strong>，不能用 <code>$host</code>（会丢弃端口）。因为后端 browser-trust fence 校验 Host 与 Origin 一致性，浏览器 Origin 带端口（<code>服务器公网地址:对外端口</code>），若 Host 无端口仍会 403。</p>
</li>
</ol>
<pre><code class="language-nginx">proxy_set_header Host $http_host;   # ✅ 保留端口
# proxy_set_header Host $host;      # ❌ $host 不含端口，会丢端口
</code></pre>
<ol>
<li>改完 service 文件后 <code>systemctl daemon-reload &amp;&amp; systemctl restart dsh-web</code>；改完 nginx 配置后 <code>nginx -t &amp;&amp; nginx -s reload</code>。</li>
</ol>
</li>
<li><p><strong>验证</strong>：非特权 API 请求恢复 <strong>200</strong>，页面基础功能可用。</p>
</li>
</ul>
<h3>问题② 控制台报 <code>crypto.randomUUID is not a function</code> → sub_filter 注入 polyfill</h3>
<ul>
<li><p><strong>现象</strong>：前端控制台（Console）报 <code>Uncaught TypeError: crypto.randomUUID is not a function</code>，页面部分功能（依赖随机 UUID 的逻辑）不可用。</p>
</li>
<li><p><strong>根因</strong>：部分浏览器环境未实现 <code>crypto.randomUUID</code>，调用即抛 TypeError。</p>
</li>
<li><p><strong>修复</strong>：用 nginx <code>sub_filter</code> 在返回的 HTML <code>&lt;/head&gt;</code> 前注入一个基于 <code>crypto.getRandomValues</code> 的 UUID v4 polyfill（完整代码见第四节 <code>location /</code>）：</p>
<pre><code class="language-nginx">proxy_set_header Accept-Encoding "";   # 必须：让后端返回未压缩 HTML，sub_filter 才能匹配
sub_filter '&lt;/head&gt;' '&lt;/head&gt;&lt;script&gt;(function(){...})();&lt;/script&gt;';
sub_filter_once on;
</code></pre>
<ul>
<li><strong><code>proxy_set_header Accept-Encoding "";</code> 是必须的</strong>：否则后端返回 gzip 压缩的 HTML，<code>sub_filter</code> 无法匹配到 <code>&lt;/head&gt;</code>，注入不生效；</li>
<li><strong><code>sub_filter_once on;</code></strong>：只在第一个 <code>&lt;/head&gt;</code> 处注入一次；</li>
<li><strong>不要显式声明 <code>sub_filter_types text/html</code></strong>：会与全局 mime.types 重复定义，产生 <code>nginx -t</code> 警告。</li>
</ul>
</li>
<li><p><strong>验证</strong>：polyfill 注入生效，页面无 JS 报错，Console 干净。</p>
</li>
</ul>
<h3>问题③ <code>/api/settings.describe</code> 等特权方法仍返回 403 → location /api 伪造回环 Host/Origin</h3>
<ul>
<li><p><strong>现象</strong>：已配置 <code>--trusted-host</code> 后，非特权 API 已恢复 200，但 <code>settings.describe</code>、<code>settings.update</code>、<code>credentials.set</code> 等<strong>特权方法</strong>仍返回 403，即使带认证凭据访问也一样。</p>
</li>
<li><p><strong>根因</strong>：DeepSeek Harness 将 <code>settings.describe</code>、<code>settings.update</code>、<code>credentials.set</code> 等敏感 API 注册在 loopback 信任通道（<code>authority: 'loopback'</code>，见 <code>packages/client/connection/src/rpc-host.ts</code>）上，<strong>强制要求请求来自回环地址</strong>（loopback fence），这是 DeepSeek Harness 的<strong>故意安全设计</strong>——<code>--trusted-host</code> 只是防止 DNS-rebinding 的 Host 校验，<strong>并非认证层</strong>，因此<strong>无法放行特权方法</strong>。要让公网请求通过，必须让后端「以为」请求来自回环。</p>
</li>
<li><p><strong>修复</strong>：新增独立的 <code>location /api</code>，转发时<strong>伪造回环 Host/Origin</strong>：</p>
<pre><code class="language-nginx">location /api {
    proxy_pass http://127.0.0.1:后端端口;

    # 伪造回环 Host/Origin，使特权方法通过 DeepSeek Harness 回环 fence
    proxy_set_header Host 127.0.0.1:后端端口;
    proxy_set_header Origin http://127.0.0.1:后端端口;
    # ... 其余反代头与 WebSocket 配置同 location /
}
</code></pre>
<ul>
<li><strong><code>Origin</code> 必须硬编码</strong>为 <code>http://127.0.0.1:后端端口</code> 与 <code>Host</code> 保持一致；若用 <code>$http_origin</code> 携带公网原始 Origin，仍会因与 Host 不一致而 403；</li>
<li><code>/api/events.mux</code>、<code>/api/events.host</code> 等 <strong>WebSocket 端点也走本 location</strong>，loopback 为最高信任，升级握手仍能通过 fence；</li>
<li><strong>权衡</strong>：这一步等于<strong>绕过了 DeepSeek Harness 内置的回环保护</strong>，真实的访问认证完全由 server 级 <strong>Basic Auth</strong> 承担——即「Basic Auth 是替代认证方案」。<strong>安全权衡必须明确记录</strong>：一旦凭据泄露，任何通过认证的请求都可调用特权方法，凭据强度就是最后一道防线。</li>
</ul>
</li>
<li><p><strong>验证</strong>：浏览器访问 <code>settings.describe</code>、<code>session.history</code> 等请求<strong>全部返回 200</strong>，页面完整功能可用。</p>
</li>
</ul>
<blockquote>
<p>[!TIP] 建议
排障时按「信任链」逐层排查：先看 Host 信任（trustedHosts），再看浏览器兼容性（randomUUID），最后看回环信任（loopback fence）。每层打通后验证一次，能少走很多弯路。</p>
</blockquote>
<h3>3、方案选择：Nginx 反代 + Basic Auth</h3>
<blockquote>
<p><strong>公网 IP + 对外端口 + Basic Auth</strong>，由 Nginx 反代到仅监听回环地址的后端 DeepSeek Harness 的 web 服务。</p>
</blockquote>
<p>整体架构如下：</p>
<pre><code>浏览器 ──► Nginx(服务器公网地址:对外端口) ──反代──► DeepSeek Harness web(127.0.0.1:后端端口)
        │                                                       │
        └── Basic Auth                                         └── --trusted-host
</code></pre>
<p>方案要点：</p>
<ul>
<li><strong>对内</strong>：DeepSeek Harness 的 web 服务默认只监听 <code>127.0.0.1:后端端口</code>（有意不支持 <code>--host 0.0.0.0</code>），后端不直接暴露网卡，对外统一交给 Nginx，这个设计其实更安全；</li>
<li><strong>对外</strong>：Nginx 监听 <code>对外端口</code>，叠加 Basic Auth 做访问认证；</li>
</ul>
<hr />
<h2>二、环境准备</h2>
<h3>1、克隆官方仓库并固定 commit</h3>
<p>克隆 DeepSeek Harness 官方仓库，并固定到一个已知正常的提交，避免上游漂移导致构建问题：</p>
<pre><code class="language-sh"># 克隆自 deepseek-harness 官方仓库，固定提交
git clone https://github.com/deepseek-ai/deepseek-harness.git 部署目录/deepseek-harness
cd 部署目录/deepseek-harness
git checkout 47f943859b    # Merge pull request #2519 from deepseek-harness/feat/npm-public
</code></pre>
<blockquote>
<p>[!TIP] 建议
固定 commit 是个好习惯。仓库主分支每天都在变，今天能构建、下周可能就挂了。锁定一个验证过的提交，后续升级再手动解锁，可控性高很多。</p>
</blockquote>
<h3>2、Node 与 pnpm 工具链</h3>
<p>仓库根目录 <code>package.json</code> 声明了 <code>"packageManager": "pnpm@11.7.0"</code>，而系统预装的 pnpm 是 <strong>10.9.0</strong>。执行 <code>pnpm install</code> 时 pnpm 检测到版本与声明不符，会触发「自动切换到声明版本」的流程，但该流程依赖 corepack，而这个环境里 <strong>corepack 不可用</strong>，于是安装流程直接卡死。</p>
<p><strong>解法：直接把全局 pnpm 升级到仓库声明的版本</strong>（旧版备份保留，可随时回退）：</p>
<pre><code class="language-sh"># 1) 升级全局 pnpm 至 11.7.0（旧版 10.9.0 已备份为 pnpm.bak-10.9.0，未删除，可随时回退）
npm install -g pnpm@11.7.0
pnpm -v   # 输出 v11.7.0 ✅

# 2) registry 切换为 npmmirror 国内镜像（加速依赖下载）
npm config set registry https://registry.npmmirror.com/
npm config get registry   # 输出 https://registry.npmmirror.com/ ✅
</code></pre>
<blockquote>
<p>[!TIP] 建议
升级前把旧版 pnpm 二进制备份一份（改名保留即可，别删），万一新版本有问题还能秒回退。真出问题的时候，你会发现这个备份有多香。</p>
</blockquote>
<h3>3、依赖安装与构建（小内存服务器务必限制堆内存）</h3>
<pre><code class="language-sh">cd 部署目录/deepseek-harness
pnpm install              # 使用 pnpm-lock.yaml 锁定版本，安装成功

# 构建：服务器内存紧张，必须限制 Node 堆内存，避免 OOM
NODE_OPTIONS="--max-old-space-size=2048" pnpm run build
</code></pre>
<blockquote>
<p>[!CAUTION] 注意
<code>build</code> 会执行 host + client 两套 <code>tsc -b</code>、tsdown 打包以及前端 Vite 构建，全是内存密集型的 Node 进程，对内存非常敏感。直接裸跑大概率看到 <code>FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory</code>，严重时系统 OOM Killer 直接杀进程。若仍报内存不足，可以降到 <code>--max-old-space-size=1536</code>（代价是构建时间变长），<strong>不建议</strong>超过 2048，不然容易把整台机器 OOM。</p>
</blockquote>
<hr />
<h2>三、systemd 服务化</h2>
<p>用 systemd 托管 DeepSeek Harness 的 web 进程，支持开机自启与异常自动拉起，比裸跑 <code>nohup</code> 省心。</p>
<h3>1、服务文件：<code>dsh-web.service</code></h3>
<p>在 systemd 服务目录（<code>/etc/systemd/system/</code> 是 systemd 服务的标准存放位置）创建 <code>dsh-web.service</code>：</p>
<pre><code class="language-ini">[Unit]
Description=DeepSeek Harness Web UI
After=network.target

[Service]
Type=simple
WorkingDirectory=部署目录/deepseek-harness
Environment=NODE_OPTIONS=--max-old-space-size=2048
# 同时信任带端口与无端口两种 Host 形态：
# - nginx 的 $host 变量不含端口，实际转发 Host 为 服务器公网地址
# - 浏览器/直连可能携带 Host: 服务器公网地址:对外端口
ExecStart=pnpm dsh web --trusted-host 服务器公网地址:对外端口 --trusted-host 服务器公网地址
Restart=on-failure
RestartSec=5
User=root
# 日志追加写入 /var/log/dsh-web.log
StandardOutput=append:/var/log/dsh-web.log
StandardError=append:/var/log/dsh-web.log

[Install]
WantedBy=multi-user.target
</code></pre>
<p>加载并启动：</p>
<pre><code class="language-sh">systemctl daemon-reload
systemctl enable dsh-web    # 开机自启
systemctl start dsh-web
systemctl status dsh-web    # active (running) ✅
</code></pre>
<h3>2、关键点说明</h3>
<ul>
<li><strong>监听地址</strong>：DeepSeek Harness 的 web 服务默认只监听 <code>127.0.0.1:后端端口</code>（有意不支持 <code>--host 0.0.0.0</code>），对外统一交给 Nginx 反代，这个设计其实更安全——后端不直接暴露网卡。</li>
<li><strong><code>--trusted-host</code> 参数（解决 <code>/api</code> 403）</strong>：同时传入带端口（<code>服务器公网地址:对外端口</code>）与不带端口（<code>服务器公网地址</code>）两种形态，确保 nginx 的 <code>$host</code> 转发头与浏览器直连 Host 都能通过 DeepSeek Harness 的 Host 信任校验（trustedHosts），避免非特权 API 因 Host 不符返回 403。具体原理见第一节「2、踩坑记录」问题①。</li>
<li><strong>开机自启</strong>：<code>systemctl enable dsh-web</code> 后状态为 <code>enabled</code>。</li>
</ul>
<blockquote>
<p>[!CAUTION] 注意
改完 service 文件记得 <code>daemon-reload</code>！改完记得 <code>daemon-reload</code>！改完记得 <code>daemon-reload</code>！这个忘了，改十遍配置都不会生效。</p>
</blockquote>
<hr />
<h2>四、Nginx 反代配置</h2>
<p>配置文件：<strong><code>dsh.conf</code></strong>，放在 Nginx 站点配置目录下（即 Nginx 会 include 进来的站点配置目录，比如 <code>/etc/nginx/conf.d/</code> 这类标准位置）。</p>
<h3>1、对外端口块（核心对外入口）</h3>
<pre><code class="language-nginx">server {
    listen 对外端口;
    server_name _;

    # DeepSeek Harness 应用访问日志（通用日志目录，可自行调整）
    access_log /var/log/nginx/dsh-access.log;
    error_log /var/log/nginx/dsh-error.log;

    # Basic Auth 访问认证（仅对外入口端口生效，后端监听地址不受影响）
    # 注意: 密码文件必须放在 nginx worker 可读的路径下
    #（Nginx 站点配置目录权限通常是 700 root，nginx worker 进程读不到）
    auth_basic "DeepSeek Harness Restricted Access";
    auth_basic_user_file 部署目录/dsh-auth/dsh-htpasswd;

    # DeepSeek Harness 可能上传文件 / Agent 任务产物，放宽请求体大小
    client_max_body_size 20m;

    # ---- location / ：反代 + WebSocket + sub_filter 注入 polyfill ----
    location / {
        proxy_pass http://127.0.0.1:后端端口;

        # 让后端返回未压缩的 HTML，确保 sub_filter 能注入 polyfill
        proxy_set_header Accept-Encoding "";

        # 兼容部分浏览器对 crypto.randomUUID 的支持差异
        # (部分环境未实现 crypto.randomUUID，改用 getRandomValues 模拟)
        sub_filter '&lt;/head&gt;' '&lt;/head&gt;&lt;script&gt;(function(){try{if(typeof crypto!=="undefined"&amp;&amp;crypto&amp;&amp;!crypto.randomUUID){crypto.randomUUID=(function(){function b(a){return a.toString(16).padStart(2,"0")}return function(){var c=new Uint8Array(16);crypto.getRandomValues(c);c[6]=(c[6]&amp;0x0f)|0x40;c[8]=(c[8]&amp;0x3f)|0x80;var h="";for(var i=0;i&lt;16;i++){h+=b(c[i]);if(i===3||i===5||i===7||i===9){h+="-"}}return h}})()}}catch(e){}})();&lt;/script&gt;';
        sub_filter_once on;

        # 必须用 $http_host（保留端口），不能用 $host（会丢弃端口）。
        # 后端 DeepSeek Harness 的 browser-trust fence 校验 Host 与 Origin 一致性，
        # 浏览器 Origin 带端口(服务器公网地址:对外端口)，若 Host 无端口会 403。
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket 支持（DeepSeek Harness 前端实时通信/事件推送）
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 长连接/超时设置（Agent 任务长时运行，务必给足）
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_connect_timeout 30s;
    }

    # ---- location /api ：伪造回环 Host/Origin（特权方法信任）----
    location /api {
        proxy_pass http://127.0.0.1:后端端口;

        # 伪造回环 Host/Origin，使特权方法通过 DeepSeek Harness 回环 fence
        proxy_set_header Host 127.0.0.1:后端端口;
        proxy_set_header Origin http://127.0.0.1:后端端口;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # WebSocket 支持（/api/events.* 升级握手）
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_connect_timeout 30s;
    }
}
</code></pre>
<h3>2、Basic Auth 凭据</h3>
<ul>
<li><strong>账号</strong>：<code>admin</code></li>
<li><strong>密码</strong>：<code>you_passwd</code>（占位，部署时务必替换为自设的强密码，不要沿用任何示例密码）</li>
<li><strong>密码文件</strong>：<code>dsh-htpasswd</code>，放在「Nginx worker 可读的目录」下（如 <code>部署目录/dsh-auth/dsh-htpasswd</code>）</li>
</ul>
<p>生成密码文件：</p>
<pre><code class="language-sh">mkdir -p 部署目录/dsh-auth
# 生成 Basic Auth 密码文件（账号 admin，密码 you_passwd，htpasswd 加密格式）
htpasswd -bc 部署目录/dsh-auth/dsh-htpasswd admin you_passwd
# 调整属主与权限：root:nginx，nginx worker 可读
chown root:nginx 部署目录/dsh-auth/dsh-htpasswd
chmod 640 部署目录/dsh-auth/dsh-htpasswd

ls -l 部署目录/dsh-auth/dsh-htpasswd
# 期望：-rw-r----- root nginx  dsh-htpasswd（nginx worker 可读 ✅）
</code></pre>
<blockquote>
<p>[!CAUTION] 注意
<strong>密码文件路径是第一个坑</strong>：<code>dsh-htpasswd</code> 必须放在 <strong>nginx worker 可读的目录</strong>下，<strong>不能</strong>放在 Nginx 站点配置目录里。因为站点配置目录权限通常是 <code>700 root</code>，nginx worker 进程（以 nginx 用户运行）读不到，会导致所有请求直接 <strong>500</strong>，Nginx 错误日志里会看到 <code>open() "...dsh-htpasswd" failed (13: Permission denied)</code>。</p>
</blockquote>
<h3>3、<code>location /api</code> 为什么要伪造回环 Host/Origin（原理与权衡，务必理解）</h3>
<ul>
<li>DeepSeek Harness 有意设计了一个<strong>回环信任围栏（loopback fence）</strong>：<code>settings.describe</code>、<code>settings.update</code>、<code>credentials.set</code> 等敏感 API 注册在 loopback 信任通道（<code>authority: 'loopback'</code>，见 <code>packages/client/connection/src/rpc-host.ts</code>），<strong>强制要求请求来自回环地址（127.0.0.1 / localhost）</strong>。这是 DeepSeek Harness 的<strong>故意安全设计</strong>——即使 <code>--trusted-host</code> 也只是防止 DNS-rebinding 的 Host 校验，<strong>并非认证层</strong>，无法放行特权方法。</li>
<li>因此 nginx 在 <code>location /api</code> 中把转发请求的 <code>Host</code> 与 <code>Origin</code> <strong>伪造为 <code>127.0.0.1:后端端口</code></strong>，让特权方法误以为请求来自回环，从而通过 fence。</li>
<li><strong>权衡</strong>：这一步等于<strong>绕过了 DeepSeek Harness 内置的回环保护</strong>，真实的访问认证由 server 级 <strong>Basic Auth</strong> 承担——即「Basic Auth 是替代认证方案」。</li>
<li>注意 <code>/api/events.mux</code>、<code>/api/events.host</code> 等 WebSocket 端点也走本 location，升级握手依然能通过 fence。</li>
<li><strong>Origin 必须硬编码</strong>为 <code>http://127.0.0.1:后端端口</code> 与 Host 保持一致；若用 <code>$http_origin</code> 携带公网原始 Origin，反而会 403。</li>
</ul>
<blockquote>
<p>[!CAUTION] 注意
这一步<strong>绕过</strong>了 DeepSeek Harness 内置的回环保护，属于安全权衡手段。<strong>仅建议在受控环境使用</strong>，实际认证完全依赖 Basic Auth。一旦放开，任何通过认证的请求都可以调用特权方法，所以 Basic Auth 的凭据强度就是最后一道防线。</p>
</blockquote>
<h3>4、生效确认</h3>
<pre><code class="language-sh">nginx -t &amp;&amp; nginx -s reload   # 语法校验通过后重载
</code></pre>
<blockquote>
<p>[!CAUTION] 注意
改完 nginx 配置一定要先 <code>nginx -t</code> 再 <code>reload</code>！语法错了直接全站 502 的教训，谁踩谁知道。另外，如果显式写了 <code>sub_filter_types text/html;</code>，<code>nginx -t</code> 会报「duplicate MIME type "text/html"」警告——<code>sub_filter</code> 默认只处理 <code>text/html</code>，<strong>不要</strong>显式声明这一行。</p>
</blockquote>
<hr />
<h2>五、远程访问控制</h2>
<h3>1、浏览器访问入口</h3>
<p>在任意设备（笔记本、手机、公司电脑）的浏览器打开：</p>
<pre><code>服务器公网地址:对外端口
</code></pre>
<p>浏览器会弹出 Basic Auth 登录框，输入：</p>
<ul>
<li>账号：<code>admin</code></li>
<li>密码：<code>you_passwd</code>（你部署时设置的真实密码）</li>
</ul>
<p>登录成功后进入 DeepSeek Harness Web UI，即可远程操作。</p>
<h3>2、验证链路（命令与预期输出）</h3>
<pre><code class="language-sh"># 无凭据访问 → 401（认证生效）
curl -I 服务器公网地址:对外端口
# HTTP/1.1 401 Unauthorized  ✅

# 带凭据访问 → 200
curl -I -u admin:you_passwd 服务器公网地址:对外端口
# HTTP/1.1 200 OK  ✅
</code></pre>
<h3>3、WebSocket 实时通信</h3>
<p>DeepSeek Harness 前端与后端通过 WebSocket 保持长连接，用于<strong>任务进度实时推送、Agent 执行日志流式输出、前端事件总线</strong>。这依赖 Nginx 正确转发升级握手：</p>
<pre><code class="language-nginx">proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
</code></pre>
<p>如果漏掉这两行，页面能打开但<strong>实时数据不更新</strong>，任务跑起来看不到进度，表现为「页面死了一样」。排障时优先检查这个。</p>
<h3>4、页面主要功能如何远程操作</h3>
<table>
<thead>
<tr>
<th>功能</th>
<th>说明</th>
<th>远程操作方式</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Agent 会话</strong></td>
<td>与 Agent 对话、下发任务指令</td>
<td>新建/切换会话 → 输入指令 → Agent 执行过程实时回流到页面</td>
</tr>
<tr>
<td><strong>评测任务</strong></td>
<td>创建并运行评测，查看结果</td>
<td>配置评测参数 → 触发运行 → 通过 WebSocket 实时看进度 → 完成后查看结果</td>
</tr>
<tr>
<td><strong>session.history</strong></td>
<td>会话历史记录查询</td>
<td>页面内直接浏览历史会话，跨设备共享，工作记录不丢</td>
</tr>
<tr>
<td><strong>文件/产物</strong></td>
<td>Agent 任务产物、上传文件</td>
<td>页面内上传/下载（依赖 <code>client_max_body_size 20m</code> 放宽请求体）</td>
</tr>
</tbody></table>
<blockquote>
<p>[!TIP] 建议
远程操作的核心体验差异就是「实时性」：WebSocket 断流 → 进度卡住；Host 头不对 → 接口 403；浏览器兼容性 → 前端 JS 报错。把这三点提前想明白，远程使用基本就顺畅了。</p>
</blockquote>
<hr />
<h2>六、管理 deepseek-harness</h2>
<h3>1、管理脚本：<code>dsh-manage.sh</code></h3>
<p>四合一管理脚本，基于 systemd，提供 start / stop / restart / status 四个操作。放在项目目录下即可：</p>
<pre><code class="language-bash">#!/bin/bash
# 用法:
#   bash dsh-manage.sh start    开启 DeepSeek Harness
#   bash dsh-manage.sh stop     关闭 DeepSeek Harness
#   bash dsh-manage.sh restart  重启 DeepSeek Harness
#   bash dsh-manage.sh status   查看运行状态

SERVICE="dsh-web"          # DeepSeek Harness 的 systemd 服务名
WAIT_SECONDS=5             # 操作后等待秒数
URL="服务器公网地址:对外端口"

case "$1" in
  start)
    echo "&gt;&gt;&gt; 正在开启 DeepSeek Harness ..."
    systemctl daemon-reload
    systemctl start "$SERVICE"
    sleep "$WAIT_SECONDS"
    if systemctl is-active --quiet "$SERVICE"; then
      echo "[OK] 已开启，访问地址: $URL"
    else
      echo "[ERROR] 开启失败，最近日志:"
      journalctl -u "$SERVICE" -n 20 --no-pager
      exit 1
    fi
    ;;
  stop)
    echo "&gt;&gt;&gt; 正在关闭 DeepSeek Harness ..."
    systemctl stop "$SERVICE"
    sleep 2
    if systemctl is-active --quiet "$SERVICE"; then
      echo "[ERROR] 正常停止失败，强制终止..."
      systemctl kill "$SERVICE"
      sleep 2
    fi
    if systemctl is-active --quiet "$SERVICE"; then
      echo "[ERROR] 关闭失败，请手动检查: journalctl -u $SERVICE"
      exit 1
    else
      echo "[OK] 已关闭"
    fi
    ;;
  restart)
    echo "&gt;&gt;&gt; 正在重启 DeepSeek Harness ..."
    systemctl daemon-reload
    systemctl restart "$SERVICE"
    sleep "$WAIT_SECONDS"
    if systemctl is-active --quiet "$SERVICE"; then
      echo "[OK] 已重启，访问地址: $URL"
    else
      echo "[ERROR] 重启失败，最近日志:"
      journalctl -u "$SERVICE" -n 20 --no-pager
      exit 1
    fi
    ;;
  status)
    systemctl status "$SERVICE" --no-pager
    ;;
  *)
    echo "用法: bash $0 {start|stop|restart|status}"
    exit 1
    ;;
esac
exit 0
</code></pre>
<p>使用与验证：</p>
<pre><code class="language-sh">bash -n 部署目录/dsh-manage.sh                    # 语法检查通过（无输出）
chmod +x 部署目录/dsh-manage.sh
bash 部署目录/dsh-manage.sh start                 # 开启
bash 部署目录/dsh-manage.sh restart               # 重启验证
bash 部署目录/dsh-manage.sh status                # 状态
</code></pre>
<blockquote>
<p>[!NOTE] 提示
赋予执行权限后即可用 <code>bash dsh-manage.sh {start|stop|restart|status}</code> 调用；也可在**系统定时任务（cron）**中配置 Shell 脚本定时调用，实现巡检 / 守护。</p>
</blockquote>
<blockquote>
<p>例如在任务调度系统中配置如下定时任务：</p>
<p><img src="https://www.hellorx.site/_astro/deepseek-harness-cron-tasks.CcIHcMYk_1Qhnsc.webp" alt="DeepSeek Harness 与 Nginx 的定时任务调度列表" loading="lazy" /></p>
</blockquote>
<h3>2、日志位置速查</h3>
<table>
<thead>
<tr>
<th>日志</th>
<th>位置</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>DeepSeek Harness 应用日志</td>
<td><code>/var/log/dsh-web.log</code></td>
<td>systemd 服务的 stdout/stderr（追加写入）</td>
</tr>
<tr>
<td>Nginx 访问日志</td>
<td><code>/var/log/nginx/dsh-access.log</code></td>
<td>对外端口全部请求</td>
</tr>
<tr>
<td>Nginx 错误日志</td>
<td><code>/var/log/nginx/dsh-error.log</code></td>
<td>Nginx 层错误（含代理 / 认证问题）</td>
</tr>
<tr>
<td>systemd 日志</td>
<td><code>journalctl -u dsh-web</code></td>
<td>systemd 托管日志（管理脚本报错时使用）</td>
</tr>
</tbody></table>
<pre><code class="language-sh">tail -f /var/log/nginx/dsh-access.log   # 实时看访问
tail -f /var/log/dsh-web.log            # 实时看应用日志
journalctl -u dsh-web -n 50 --no-pager  # 看 systemd 日志
</code></pre>
<h3>3、常见操作速查</h3>
<table>
<thead>
<tr>
<th>操作</th>
<th>命令</th>
</tr>
</thead>
<tbody><tr>
<td>修改 DeepSeek Harness 服务配置</td>
<td>编辑 <code>dsh-web.service</code> → <code>systemctl daemon-reload</code> → <code>systemctl restart dsh-web</code></td>
</tr>
<tr>
<td>修改 Nginx 配置</td>
<td>编辑 <code>dsh.conf</code> → <code>nginx -t</code> → <code>nginx -s reload</code></td>
</tr>
<tr>
<td>修改 Basic Auth 密码</td>
<td><code>htpasswd -B 部署目录/dsh-auth/dsh-htpasswd admin</code> → <code>nginx -s reload</code>（保持权限 <code>-rw-r----- root nginx</code>）</td>
</tr>
<tr>
<td>查看端口占用</td>
<td><code>ss -lntp</code></td>
</tr>
<tr>
<td>查看内存</td>
<td><code>free -h</code></td>
</tr>
</tbody></table>
<blockquote>
<p>记住两件事：<strong>改 systemd 必须 <code>daemon-reload</code>；改 Nginx 必须先 <code>nginx -t</code> 再 reload</strong>。</p>
</blockquote>
<hr />
<h2>七、总结</h2>
<p>这次云部署 DeepSeek Harness 的最终方案可以概括为三句话：</p>
<ul>
<li><strong>对外</strong>：Nginx + 对外端口 + Basic Auth 访问认证；</li>
<li><strong>对内</strong>：DeepSeek Harness 的 web 服务只监听回环地址，由 Nginx 反代，systemd 托管守护；</li>
<li><strong>排障</strong>：三次典型问题（Host 信任、randomUUID 兼容性、回环 fence）本质是一条信任链的逐层打通。</li>
</ul>
<p>整套方案的关键文件如下，方便日后速查：</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>位置 / 值</th>
</tr>
</thead>
<tbody><tr>
<td>仓库目录</td>
<td><code>部署目录/deepseek-harness</code>（固定提交 <code>47f943859b</code>）</td>
</tr>
<tr>
<td>Nginx 配置</td>
<td>Nginx 站点配置目录下的 <code>dsh.conf</code></td>
</tr>
<tr>
<td>Basic Auth 密码文件</td>
<td><code>部署目录/dsh-auth/dsh-htpasswd</code>（账号 <code>admin</code>）</td>
</tr>
<tr>
<td>systemd 服务</td>
<td><code>/etc/systemd/system/dsh-web.service</code>（服务名 <code>dsh-web</code>）</td>
</tr>
<tr>
<td>管理脚本</td>
<td><code>部署目录/dsh-manage.sh</code></td>
</tr>
<tr>
<td>应用日志</td>
<td><code>/var/log/dsh-web.log</code></td>
</tr>
<tr>
<td>Nginx 日志</td>
<td><code>/var/log/nginx/dsh-access.log</code>、<code>/var/log/nginx/dsh-error.log</code></td>
</tr>
<tr>
<td>后端监听</td>
<td><code>127.0.0.1:后端端口</code></td>
</tr>
<tr>
<td>对外访问</td>
<td><code>服务器公网地址:对外端口</code>（Basic Auth：<code>admin</code> / 自设强密码）</td>
</tr>
</tbody></table>
<hr />
<h2>八、最终效果</h2>
<p>部署完成后，浏览器打开 <code>服务器公网地址:对外端口</code>，输入 Basic Auth 凭据登录，即可进入 DeepSeek Harness Web UI 远程操控 Agent：</p>
<p><img src="https://www.hellorx.site/_astro/deepseek-harness-webui-agent.CmwkqoWj_Z2fGAFR.webp" alt="DeepSeek Harness Web UI 对话界面：Agent 执行系统检查与工具调用" loading="lazy" /></p>
]]></content:encoded></item><item><title>半小时入门具身智能：手动创建第一个 Isaac Lab 项目</title><link>https://www.hellorx.site/posts/ai/ai-embodied-isaaclab-first-project/</link><guid isPermaLink="true">https://www.hellorx.site/posts/ai/ai-embodied-isaaclab-first-project/</guid><description>具身智能零基础入门教程，从创建 IsaacSim 仿真项目、完成 MDP 配置与 Gym 环境注册，到训练、验证、可视化及奖励函数优化的完整闭环。</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 提示
本文完整走一遍从创建 IsaacSim 仿真项目，到完成基本项目配置（MDP 配置、机器人模型配置、Gym 接口注册），再到训练、验证及可视化，最后完成策略优化的闭环流程。文章基于宇树 Go2 机器人，完整 Demo 已开源在 <a href="https://github.com/menoking/Go2-Demo">menoking/Go2-Demo</a>。主要参考猫师傅的《米雪儿教你从零开始编写 Isaac Lab 强化学习环境》教程以及 <a href="https://isaac-sim.github.io/IsaacLab/main/source/setup/quickstart.html">Isaac Lab 官方 Quickstart Guide</a>。</p>
</blockquote>
<h2>一、基础知识</h2>
<h3>1、强化学习扫盲</h3>
<p>强化学习（RL）：通过让智能体（Agent）与环境（Environment）交互，学习出能够最大化累积奖励（Cumulative Reward）的策略（Policy）。</p>
<p>核心四要素：</p>
<ul>
<li>智能体 Agent</li>
<li>奖励 Reward</li>
<li>环境 Environment</li>
<li>策略 Policy</li>
</ul>
<h3>2、领域术语</h3>
<p>机器人领域常见的文件格式：</p>
<table>
<thead>
<tr>
<th>格式</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td><strong>USD</strong></td>
<td>皮克斯开发的通用场景描述框架，以分层、非破坏式编辑为核心，是 Isaac Sim 的原生格式，支持复杂材质、动画和大规模场景合成</td>
</tr>
<tr>
<td><strong>URDF</strong></td>
<td>基于 XML 的机器人描述格式，定义连杆、关节、运动学和动力学属性，是 ROS 生态中机器人的标准表示</td>
</tr>
<tr>
<td><strong>SDF</strong></td>
<td>Gazebo 专用的仿真描述格式，能完整描述整个世界（多机器人、地形、传感器、灯光等），比 URDF 更强大</td>
</tr>
<tr>
<td><strong>MJCF</strong></td>
<td>MuJoCo 物理引擎的原生 XML 格式，描述紧凑、接触建模高效，广泛用于高频控制和足式机器人仿真</td>
</tr>
<tr>
<td><strong>XACRO</strong></td>
<td>URDF 的宏语言扩展，支持参数化、模块化生成 URDF 文件，用于避免重复代码</td>
</tr>
<tr>
<td><strong>STL / OBJ / COLLADA (DAE)</strong></td>
<td>仅用于存储 3D 网格几何数据的静态格式，不包含运动学或动力学信息，常被 URDF/USD 作为视觉或碰撞网格引用</td>
</tr>
<tr>
<td><strong>GLTF / GLB</strong></td>
<td>面向实时渲染和 Web 传输的 3D 格式，集成了网格、材质、动画和皮肤，在图形交换中越来越普及</td>
</tr>
</tbody></table>
<h3>3、MDP 与 Isaac Lab 工作流</h3>
<p><strong>MDP（马尔可夫决策过程）</strong> 是强化学习问题形式化的数学基础，也是 Isaac Lab 中每个环境（<code>Env</code>）核心逻辑的设计依据。</p>
<ul>
<li>在 <strong>Manager-based</strong> 工作流中，MDP 的各部分被拆解为独立的配置模块：<ul>
<li><strong>Observation Manager</strong>：定义如何从物理状态映射到观测。</li>
<li><strong>Action Manager</strong>：定义动作空间及映射到实际控制量。</li>
<li><strong>Reward Manager</strong>：累加多个奖励项。</li>
<li><strong>Termination Manager</strong>：判断任务失败/成功条件。</li>
</ul>
</li>
<li>在 <strong>Direct</strong> 工作流中，直接覆写 <code>_get_observations()</code>、<code>_get_rewards()</code>、<code>_get_done_terminations()</code> 等方法。</li>
</ul>
<h2>二、项目创建</h2>
<h3>1、创建项目</h3>
<p>键入以下命令新建项目：</p>
<pre><code class="language-bash">isaaclab.bat -n
</code></pre>
<p>进入配置页面（<strong>上下箭头移动，空格选中，回车确认</strong>）：</p>
<ul>
<li><strong>工作流</strong>：Manager-based 是官方推荐的声明式主流框架，模块化结构化，适合复杂任务、快速原型开发和团队协作；Direct 是命令式框架，灵活自由度高，适合简单精细任务。</li>
<li><strong>RL 框架</strong>：<ul>
<li><strong>rsl-rl</strong>：简单、专注且高效，是足式机器人任务快速验证的首选方案，由 ETH Zurich 研发，核心理念是"提供紧凑且易于修改的代码库"。</li>
<li><strong>SKRL（Skrl）</strong>：灵活多样的"瑞士军刀"，提供 PPO、SAC 等丰富算法库，适合快速横向对比研究。</li>
<li><strong>RL Games</strong>：工业级"性能猛兽"，训练效率和稳定性极佳，适合超长时间大规模训练任务。</li>
</ul>
</li>
</ul>
<p>创建成功后，安装项目扩展：</p>
<pre><code class="language-bash">python -m pip install -e source/&lt;given-project-name&gt;
</code></pre>
<blockquote>
<p>这条命令的作用：</p>
<ol>
<li>安装基础环境，确保 Python 环境能够识别和导入 Isaac Lab 的核心功能。</li>
<li>以"可编辑"模式安装项目扩展，代码的任何修改都能立即生效，无需重新安装，便于开发。</li>
</ol>
</blockquote>
<p>用 VS Code 打开项目后，定位到 Manager-based 的配置文件 <code>go2_demo_env_cfg.py</code>，并在同目录下新建自己的配置文件（这里命名为 <code>go2_demo_velocity.py</code>）。</p>
<h3>2、官方示例与仓库</h3>
<p>以下两个官方源可以提前打开，其中机器人文件来源于 <code>unitreerobotics/unitree_rl_lab</code> 项目，官方模板参考的是 IsaacLab 中的 <code>velocity_env_cfg.py</code> 文件。</p>
<ul>
<li><strong>IsaacLab 官方示例</strong>：<a href="https://github.com/isaac-sim/IsaacLab/blob/main/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py">velocity_env_cfg.py</a>，一个基于 Manager-based 工作流的四足机器人速度追踪训练环境完整配置文件，定义了宇树 Go2 机器人在复杂地形上学习奔跑的强化学习任务。</li>
<li><strong>Unitree 宇树官方仓库</strong>：<a href="https://github.com/unitreerobotics/unitree_rl_lab">unitreerobotics/unitree_rl_lab</a>，宇树机器人的强化学习专用环境仓库。</li>
</ul>
<p>官方示例代码中，各代码块对应的 MDP 要素：</p>
<table>
<thead>
<tr>
<th>代码块</th>
<th>对应 MDP 要素</th>
<th>具体内容与作用</th>
</tr>
</thead>
<tbody><tr>
<td><code>MySceneCfg</code></td>
<td>场景与环境</td>
<td>包含带纹理的粗糙地形、Go2 机器人、射线高度扫描传感器（感知地形）、接触传感器（检测脚掌/大腿触地）以及天空光照</td>
</tr>
<tr>
<td><code>CommandsCfg</code></td>
<td>高层指令（任务目标）</td>
<td>生成随机线速度命令 <code>(vx, vy)</code> 和朝向角速度命令 <code>(ang_vel_z)</code>，机器人需要追踪这个动态变化的指令，即"速度追踪任务"的核心</td>
</tr>
<tr>
<td><code>ActionsCfg</code></td>
<td>动作空间</td>
<td>输出目标关节位置（<code>JointPositionActionCfg</code>），范围缩放 0.5，使用默认偏移量（机器人站立时的关节角度）</td>
</tr>
<tr>
<td><code>ObservationsCfg</code></td>
<td>观测状态 (S)</td>
<td>定义策略网络输入，共 7 项：机体线速度/角速度、投影重力、速度命令、关节相对位置/速度、上一次动作、地形高度扫描，并对每项添加均匀噪声</td>
</tr>
<tr>
<td><code>EventCfg</code></td>
<td>域随机化与初始化</td>
<td>分三类：startup（随机化物理材质、增加机身质量、改变质心位置）、reset（随机重置机器人根部位姿、关节位置/速度）、interval（随机时间间隔用力推机器人，提高抗干扰能力）</td>
</tr>
<tr>
<td><code>RewardsCfg</code></td>
<td>奖励函数 (R)</td>
<td>速度追踪误差的指数奖励（线速度权重 1.0，角速度权重 0.5）+ 多项惩罚（竖向线速度、横向/俯仰角速度、关节力矩/加速度、动作变化率），奖励脚掌离地时间，惩罚大腿非预期触地</td>
</tr>
<tr>
<td><code>TerminationsCfg</code></td>
<td>终止条件</td>
<td>超时（步数达到 <code>episode_length_s / dt</code>）和机身触地（<code>base_contact</code>）</td>
</tr>
<tr>
<td><code>CurriculumCfg</code></td>
<td>课程学习</td>
<td>根据机器人追踪速度的表现，自动提升地形难度等级</td>
</tr>
<tr>
<td><code>LocomotionVelocityRoughEnvCfg</code></td>
<td>总配置类</td>
<td>组合上述所有模块，<code>decimation=4</code>（动作每 4 个仿真步执行一次，即控制频率 50Hz，仿真频率 200Hz），<code>episode_length_s=20.0</code></td>
</tr>
</tbody></table>
<h2>三、项目配置</h2>
<h3>1、基本配置</h3>
<p>新建 py 配置文件，导入所需包，并复制 <code>LocomotionVelocityRoughEnvCfg</code> 总配置类到自定义文件中，将类名修改为自定义名称，这里改为 <code>GO2RobotDemoEnv</code>。</p>
<h3>2、MDP 类配置</h3>
<blockquote>
<p>以下所配置的 MDP 要素的每个参数都能在 IsaacLab 官方文档中搜索到释义与用法。</p>
</blockquote>
<h4>2.1 场景配置类 scene</h4>
<p>构建包含地形、机器人和传感器的虚拟环境：</p>
<pre><code class="language-python"># 环境配置
@configclass
class RobotSceneCfg(InteractiveSceneCfg):
    # 超平坦地形
    terrain = TerrainImporterCfg(
        prim_path="/World/ground",
        terrain_type="plane",
        collision_group=-1,
        physics_material=sim_utils.RigidBodyMaterialCfg(
            friction_combine_mode="multiply",
            restitution_combine_mode="multiply",
            static_friction=1.0,
            dynamic_friction=1.0,
        ),
        visual_material=sim_utils.MdlFileCfg(
            mdl_path=f"{ISAACLAB_NUCLEUS_DIR}/Materials/TilesMarbleSpiderWhiteBrickBondHoned/TilesMarbleSpiderWhiteBrickBondHoned.mdl",
            project_uvw=True,
            texture_scale=(0.25, 0.25),
        ),
        debug_vis=False,
    )

    robot: ArticulationCfg = RobotCFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
</code></pre>
<p><strong>克隆 Go2 宇树官方 USD 文件</strong>：</p>
<pre><code class="language-bash">git clone https://huggingface.co/datasets/unitreerobotics/unitree_model
</code></pre>
<p>新建 <code>assets/robot</code> 目录（<strong>注意：assets 要和 task 同根目录</strong>），在此目录下克隆，并新建 <code>unitree.py</code> 文件对模型进行配置（注意脚本里路径应用正斜杠 <code>/</code>）：</p>
<pre><code class="language-python">import os

import isaaclab.sim as sim_utils
from isaaclab.actuators import IdealPDActuatorCfg, ImplicitActuatorCfg, DCMotorCfg
from isaaclab.assets.articulation import ArticulationCfg
from isaaclab.utils import configclass

# 定义模型路径
UNITREE_MODEL_DIR = "D:/EI/Project/go2_demo/source/go2_demo/go2_demo/assets/robot/unitree_model"

# Go2机器人配置
UNITREE_GO2_CFG = ArticulationCfg(
    # 定义加载方式
    spawn=sim_utils.UsdFileCfg(
        usd_path=f"{UNITREE_MODEL_DIR}/Go2/usd/go2.usd",  # USD文件路径
        activate_contact_sensors=True,  # 读取传感器信息
        rigid_props=sim_utils.RigidBodyPropertiesCfg(
            disable_gravity=False,
            retain_accelerations=False,
            linear_damping=0.0,
            angular_damping=0.0,
            max_linear_velocity=1000.0,
            max_angular_velocity=1000.0,
            max_depenetration_velocity=1.0,
        ),
        articulation_props=sim_utils.ArticulationRootPropertiesCfg(
            enabled_self_collisions=False,
            solver_position_iteration_count=4,
            solver_velocity_iteration_count=0,
        ),
    ),
    # 定义机器人初始状态
    init_state=ArticulationCfg.InitialStateCfg(
        pos=(0.0, 0.0, 0.4),
        joint_pos={
            ".*R_hip_joint": -0.1,
            ".*L_hip_joint": 0.1,
            "F[L,R]_thigh_joint": 0.8,
            "R[L,R]_thigh_joint": 1.0,
            ".*_calf_joint": -1.5,
        },
        joint_vel={".*": 0.0},
    ),
    # 软限位系数
    soft_joint_pos_limit_factor=0.9,
    # 核心：执行器模型
    actuators={
        "legs": DCMotorCfg(
            joint_names_expr=[".*"],
            effort_limit=23.5,  # 输出力矩上限
            saturation_effort=23.5,
            velocity_limit=30.0,
            stiffness=25.0,  # PD控制中的P
            damping=0.5,  # PD控制中的D
            friction=0.01,  # 执行器摩擦系数
        ),
    },
)
</code></pre>
<blockquote>
<p>注意：这里把宇树的子类换成对应的父类（如 <code>IdealPDActuatorCfg</code> → <code>DCMotorCfg</code>），可在 IsaacLab 中查看对应配置项的参数信息。</p>
</blockquote>
<p>回到 <code>go2_demo_velocity.py</code> 导入自定义机器人配置，并补充传感器：</p>
<pre><code class="language-python"># 导入自定义机器人配置
from go2_demo.assets.robot.unitree import UNITREE_GO2_CFG as RobotCFG

robot: ArticulationCfg = RobotCFG.replace(prim_path="{ENV_REGEX_NS}/Robot")

# 传感器
height_scanner = RayCasterCfg(
    prim_path="{ENV_REGEX_NS}/Robot/base",
    offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
    ray_alignment="yaw",
    pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
    debug_vis=False,
    mesh_prim_paths=["/World/ground"],
)
contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, track_air_time=True)

sky_light = AssetBaseCfg(
    prim_path="/World/skyLight",
    spawn=sim_utils.DomeLightCfg(
        intensity=750.0,
        texture_file=f"{ISAAC_NUCLEUS_DIR}/Materials/Textures/Skies/PolyHaven/kloofendal_43d_clear_puresky_4k.hdr",
    ),
)
</code></pre>
<h4>2.2 观测配置类 observations</h4>
<p>定义策略网络可获取的状态信息（如关节角度、速度、地形扫描）。注意先导入 mdp 包，再定义策略网络和评价网络的观测项框架：</p>
<pre><code class="language-python">@configclass
class ObservationsCfg:
    # 策略网络
    @configclass
    class PolicyCfg(ObsGroup):
        """Observations for policy group."""
        # 机体线速度
        base_lin_vel = ObsTerm(func=mdp.base_lin_vel, noise=Unoise(n_min=-0.1, n_max=0.1))
        # 机体角速度
        base_ang_vel = ObsTerm(func=mdp.base_ang_vel, noise=Unoise(n_min=-0.2, n_max=0.2))
        # 投影重力
        projected_gravity = ObsTerm(
            func=mdp.projected_gravity,
            noise=Unoise(n_min=-0.05, n_max=0.05),
        )
        # 速度指令
        velocity_commands = ObsTerm(func=mdp.generated_commands, params={"command_name": "base_velocity"})
        # 关节位置
        joint_pos = ObsTerm(func=mdp.joint_pos_rel, noise=Unoise(n_min=-0.01, n_max=0.01))
        # 关节速度
        joint_vel = ObsTerm(func=mdp.joint_vel_rel, noise=Unoise(n_min=-1.5, n_max=1.5))
        # 上一次动作
        actions = ObsTerm(func=mdp.last_action)
        # 高度扫描
        height_scan = ObsTerm(
            func=mdp.height_scan,
            params={"sensor_cfg": SceneEntityCfg("height_scanner")},
            noise=Unoise(n_min=-0.1, n_max=0.1),
            clip=(-1.0, 1.0),
        )

        def __post_init__(self):
            self.enable_corruption = True
            self.concatenate_terms = True

    # observation groups
    policy: PolicyCfg = PolicyCfg()

    # 评价网络
    @configclass
    class CriticCfg(ObsGroup):
        base_lin_vel = ObsTerm(func=mdp.base_lin_vel, noise=Unoise(n_min=-0.1, n_max=0.1))
        base_ang_vel = ObsTerm(func=mdp.base_ang_vel, noise=Unoise(n_min=-0.2, n_max=0.2))
        projected_gravity = ObsTerm(
            func=mdp.projected_gravity,
            noise=Unoise(n_min=-0.05, n_max=0.05),
        )
        velocity_commands = ObsTerm(func=mdp.generated_commands, params={"command_name": "base_velocity"})
        joint_pos = ObsTerm(func=mdp.joint_pos_rel, noise=Unoise(n_min=-0.01, n_max=0.01))
        joint_vel = ObsTerm(func=mdp.joint_vel_rel, noise=Unoise(n_min=-1.5, n_max=1.5))
        actions = ObsTerm(func=mdp.last_action)
        height_scan = ObsTerm(
            func=mdp.height_scan,
            params={"sensor_cfg": SceneEntityCfg("height_scanner")},
            noise=Unoise(n_min=-0.1, n_max=0.1),
            clip=(-1.0, 1.0),
        )

    critic: CriticCfg = CriticCfg()
</code></pre>
<h4>2.3 动作配置类 actions</h4>
<p>指定智能体输出的控制指令类型（如关节位置、速度或力矩）：</p>
<pre><code class="language-python"># 动作配置
@configclass
class ActionsCfg:
    """Action specifications for the MDP."""

    joint_pos = mdp.JointPositionActionCfg(
        asset_name="robot",
        joint_names=[".*"],
        scale=0.5,
        use_default_offset=True,
    )
</code></pre>
<h4>2.4 指令配置类 commands</h4>
<p>生成任务目标（如期望速度或朝向），供策略追踪：</p>
<pre><code class="language-python">@configclass
class CommandsCfg:
    """Command specifications for the MDP."""

    base_velocity = mdp.UniformVelocityCommandCfg(
        asset_name="robot",
        resampling_time_range=(10.0, 10.0),
        rel_standing_envs=0.02,
        rel_heading_envs=1.0,
        heading_command=True,
        heading_control_stiffness=0.5,
        debug_vis=True,
        ranges=mdp.UniformVelocityCommandCfg.Ranges(
            lin_vel_x=(-1.0, 1.0),
            lin_vel_y=(-1.0, 1.0),
            ang_vel_z=(-1.0, 1.0),
            heading=(-math.pi, math.pi),
        ),
    )
</code></pre>
<h4>2.5 奖励配置类 rewards</h4>
<p>计算每一步的奖励信号，引导策略学习期望行为：</p>
<pre><code class="language-python"># 奖励函数
@configclass
class RewardsCfg:
    track_lin_vel_xy_exp = RewTerm(
        func=mdp.track_lin_vel_xy_exp,
        weight=1.0,
        params={"command_name": "base_velocity", "std": math.sqrt(0.25)},
    )
    track_ang_vel_z_exp = RewTerm(
        func=mdp.track_ang_vel_z_exp,
        weight=0.5,
        params={"command_name": "base_velocity", "std": math.sqrt(0.25)},
    )
</code></pre>
<h4>2.6 终止条件配置类 terminations</h4>
<p>设定环境结束条件（如机器人摔倒或超时）：</p>
<pre><code class="language-python"># 终止条件
@configclass
class TerminationsCfg:
    # 时间终止
    time_out = DoneTerm(func=mdp.time_out, time_out=True)
    # 机身碰撞地面
    base_contact = DoneTerm(
        func=mdp.illegal_contact,
        params={"sensor_cfg": SceneEntityCfg("contact_forces", body_names="base"), "threshold": 1.0},
    )
    # 倾斜角度
    bad_orientation = DoneTerm(func=mdp.bad_orientation, params={"limit_angle": 0.8})
</code></pre>
<h4>2.7 事件配置类 events</h4>
<p>配置域随机化、初始状态扰动等事件，增强策略鲁棒性：</p>
<pre><code class="language-python"># 事件配置
@configclass
class EventCfg:
    reset_base = EventTerm(
        func=mdp.reset_root_state_uniform,  # 触发函数
        mode="reset",  # 触发时机--复位
        params={
            "pose_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5), "yaw": (-3.14, 3.14)},
            "velocity_range": {
                "x": (-0.0, 0.0),
                "y": (-0.0, 0.0),
                "z": (-0.0, 0.0),
                "roll": (-0.0, 0.0),
                "pitch": (-0.0, 0.0),
                "yaw": (-0.0, 0.0),
            },
        },
    )
</code></pre>
<h4>2.8 课程学习配置类 curriculum</h4>
<p>根据智能体当前表现动态调整任务难度（如地形等级），实现渐进式训练以加速策略收敛。本次暂不对其进行配置，直接注释即可。</p>
<h3>3、注册 Gym 环境</h3>
<p>复制 <code>__init__.py</code> 文件中的注册部分代码，修改如下：</p>
<pre><code class="language-python">gym.register(
    id="Go2-velocity-v0",  # 环境ID
    entry_point="isaaclab.envs:ManagerBasedRLEnv",
    disable_env_checker=True,
    kwargs={
        "env_cfg_entry_point": f"{__name__}.go2_demo_velocity:GO2RobotDemoEnv",  # 环境文件名:环境类名
        "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:PPORunnerCfg",
    },
)
</code></pre>
<h3>4、调整 PPO 网络</h3>
<p>文件位置在 <code>agents/rsl_rl_ppo_cfg.py</code>，调整至以下：</p>
<pre><code class="language-python">@configclass
class PPORunnerCfg(RslRlOnPolicyRunnerCfg):
    num_steps_per_env = 124
    max_iterations = 50000
    save_interval = 100
    experiment_name = "go2_demo"
    policy = RslRlPpoActorCriticCfg(
        init_noise_std=1.0,
        actor_hidden_dims=[512, 256, 128],
        critic_hidden_dims=[32, 32],
        activation="elu",
    )
    algorithm = RslRlPpoAlgorithmCfg(
        value_loss_coef=1.0,
        use_clipped_value_loss=True,
        clip_param=0.2,
        entropy_coef=0.005,
        num_learning_epochs=5,
        num_mini_batches=4,
        learning_rate=1.0e-3,
        schedule="adaptive",
        gamma=0.99,
        lam=0.95,
        desired_kl=0.01,
        max_grad_norm=1.0,
    )
</code></pre>
<h2>四、验证与训练</h2>
<h3>1、验证环境</h3>
<p>在 conda 环境下运行随机策略：</p>
<pre><code class="language-bash">python scripts\random_agent.py --task Go2-velocity-v0 --num_envs 14
</code></pre>
<p>跑通后机器人开始运动。再运行零策略验证（机器人静止状态）：</p>
<pre><code class="language-bash">python scripts\zero_agent.py --task Go2-velocity-v0 --num_envs 14
</code></pre>
<h3>2、正式训练</h3>
<p>无窗口渲染训练：</p>
<pre><code class="language-bash">python scripts\rsl_rl\train.py --task Go2-velocity-v0 --num_envs=400 --max_iterations 1000 --headless
</code></pre>
<h3>3、参数可视化</h3>
<p>TensorBoard 查看训练曲线：</p>
<pre><code class="language-bash">tensorboard --logdir logs
</code></pre>
<h3>4、评估策略</h3>
<p>运行下列命令，观察训练的最终模型效果并保存模型权重文件：</p>
<pre><code class="language-bash">python scripts\rsl_rl\play.py --task Go2-velocity-v0 --num_envs 16
</code></pre>
<p>在仿真器中可以观察到四足机器人的行走姿态。</p>
<h2>五、优化策略</h2>
<p>主要针对奖励函数作优化，将速度追踪权重调大，并添加了几个约束惩罚项：</p>
<pre><code class="language-python"># 奖励函数
@configclass
class RewardsCfg:
    # 线速度追踪
    track_lin_vel_xy_exp = RewTerm(
        func=mdp.track_lin_vel_xy_exp,
        weight=3.0,
        params={"command_name": "base_velocity", "std": math.sqrt(0.25)},
    )
    # 角速度追踪
    track_ang_vel_z_exp = RewTerm(
        func=mdp.track_ang_vel_z_exp,
        weight=1.5,
        params={"command_name": "base_velocity", "std": math.sqrt(0.25)},
    )
    # 约束姿态
    flat_orientation_l2 = RewTerm(func=mdp.flat_orientation_l2, weight=-2.5)
    # 限制某些部位不着地
    undesired_contacts = RewTerm(
        func=mdp.undesired_contacts,
        weight=-1.0,
        params={
            "sensor_cfg": SceneEntityCfg(
                "contact_forces",
                body_names=["Head_.*", ".*_hip", ".*thigh", ".*_calf"],
            ),
            "threshold": 1.0,
        },
    )
    # 惩罚关节速度
    joint_vel = RewTerm(func=mdp.joint_vel_l2, weight=-0.001)
    # 惩罚关节加速度
    joint_acc = RewTerm(func=mdp.joint_acc_l2, weight=-2.5e-7)
    # 惩罚输出力矩过大
    joint_torques = RewTerm(func=mdp.joint_torques_l2, weight=-2e-4)
    # 惩罚相邻时刻动作变化过大
    action_rate = RewTerm(func=mdp.action_rate_l2, weight=-0.1)
</code></pre>
<p>重新训练后再评估，四足机器人的姿态会明显改善。</p>
<h2>六、总结</h2>
<p>本文完整走了一遍 IsaacLab 中的机器人仿真训练流程，从环境搭建、MDP 配置到训练与奖励调优。主要参考 <a href="https://www.bilibili.com/video/BV1LwQ7BiEW9">猫猫烤肉师傅的 B 站免费教程</a> 与 Isaac Lab 官方文档，并对视频中未放出的代码和概念做了补充。如有错误，欢迎指正。</p>
]]></content:encoded></item><item><title>Kylin 基础环境配置</title><link>https://www.hellorx.site/posts/projects/projects-kylin-v10-basic-env-setup/</link><guid isPermaLink="true">https://www.hellorx.site/posts/projects/projects-kylin-v10-basic-env-setup/</guid><description>VMware 安装麒麟 V10 后的基础环境配置教程，覆盖主机名配置、本地镜像 yum 源搭建、iSCSI 协议 2G 存储共享、防火墙放行、vsftpd 服务部署、chrony 时间同步与 DNS 正向解析。</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>[!NOTE] 提示
本文是 VMware 安装麒麟 V10 后的基础环境配置教程，要求虚拟机处理器至少 3 核、内存 4G 以上，操作系统版本选择 Centos8。内容涵盖主机名配置、本地 yum 源搭建、iSCSI 协议 2G 存储共享、防火墙配置，以及 vsftpd 服务、chrony 时间同步、DNS 正向解析的部署操作。环境相关命令均在麒麟 V10（Centos8 系）上验证。</p>
</blockquote>
<h2>一、环境准备</h2>
<h3>1、虚拟机要求</h3>
<p>VMware 安装麒麟 V10 时，虚拟机配置至少满足：</p>
<ul>
<li>处理器：至少 3 核</li>
<li>内存：4G 以上</li>
<li>操作系统版本：选择 Centos8</li>
</ul>
<h3>2、配置 IP</h3>
<p>使用 <code>nmtui</code> 进入网络配置界面，根据提供的网络信息完成 IP 配置：</p>
<pre><code class="language-bash">nmtui
</code></pre>
<h2>二、主机名配置</h2>
<p>根据网络规划，将主机名设置为规划的域名：</p>
<pre><code class="language-bash">hostnamectl set-hostname server.kylin2025.com

# 刷新 bash 会话使主机名立即生效
bash
</code></pre>
<h2>三、本地镜像 yum 源仓库</h2>
<p>挂载安装光盘，并将 yum 源指向本地镜像，避免依赖外网：</p>
<pre><code class="language-bash">mkdir /media/cdrom

mount /dev/cdrom /media/cdrom/

cd /etc/yum.repos.d/
ls

# 备份默认源
mv kylin_x86_64.repo kylin_x86_64.repo.bak

# 新建本地源
vi local.repo
</code></pre>
<p><code>local.repo</code> 内容如下：</p>
<pre><code class="language-ini">[iso]
baseurl=file:///media/cdrom
enabled=1
gpgcheck=0
</code></pre>
<h2>四、通过 iSCSI 协议共享 2G 文件存储</h2>
<h3>1、启动 target 服务</h3>
<pre><code class="language-bash">systemctl enable target --now
</code></pre>
<h3>2、进入 targetcli 配置界面</h3>
<pre><code class="language-bash">targetcli
</code></pre>
<h3>3、创建后端存储文件</h3>
<pre><code class="language-bash">/&gt; cd /backstores/fileio
/backstores/fileio&gt; create kylin-iscsi-file /root/kylin-iscsi-file 2G
</code></pre>
<h3>4、创建 iSCSI 目标</h3>
<pre><code class="language-bash">/backstores/fileio&gt; cd /iscsi
/iscsi&gt; create iqn.2025-05.com.kylin2025:storage
</code></pre>
<h3>5、创建 LUN 并关联存储文件</h3>
<pre><code class="language-bash">/iscsi&gt; cd iqn.2025-05.com.kylin2025:storage/tpg1/luns
/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/luns&gt; create /backstores/fileio/kylin-iscsi-file
</code></pre>
<h3>6、设置访问控制</h3>
<pre><code class="language-bash">/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/luns&gt; cd ../acls
/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/acls&gt; create iqn.2025-05.com.kylin2025:www
</code></pre>
<h3>7、配置监听地址与端口</h3>
<p>删除默认的 3260 端口监听，改为指定的 IP 和 3344 端口：</p>
<pre><code class="language-bash">/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/acls&gt; cd ../portals
/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/portals&gt; delete 0.0.0.0 3260
/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/portals&gt; create 192.168.100.100 3344
</code></pre>
<h3>8、保存并退出</h3>
<pre><code class="language-bash">/iscsi/iqn.2025-05.com.kylin2025:storage/tpg1/portals&gt; exit
</code></pre>
<h2>五、防火墙</h2>
<p>放行 iSCSI 监听端口并重载防火墙：</p>
<pre><code class="language-bash">firewall-cmd --permanent --add-port=3344/tcp
firewall-cmd --reload
</code></pre>
<h2>六、验证</h2>
<pre><code class="language-bash">targetcli ls
</code></pre>
<h2>七、FTP 服务</h2>
<h3>1、启用匿名登录与本地用户登录</h3>
<p>配置 vsftpd 服务，启用匿名登录和本地用户登录，匿名登录根目录为 <code>/data/ftp/</code>：</p>
<pre><code class="language-bash">vim /etc/vsftpd/vsftpd.conf
</code></pre>
<pre><code class="language-ini">anonymous_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_root=/data/
</code></pre>
<p>重启服务并调整目录权限：</p>
<pre><code class="language-bash">systemctl restart vsftpd
chown ftp /data/ -R
chmod a-w /data/
</code></pre>
<h3>2、使用 SSL 加密传输</h3>
<p>生成自签名证书：</p>
<pre><code class="language-bash">openssl req -new -x509 -nodes -out vsftpd.pem -keyout vsftpd.pem -days 365
</code></pre>
<blockquote>
<p>证书信息按提示填写即可，国家填 <code>CN</code>，其余可以直接回车跳过。</p>
</blockquote>
<p>移动证书到指定目录并修改配置：</p>
<pre><code class="language-bash">mkdir /etc/vsftpd/.ssl
mv vsftpd.pem /etc/vsftpd/.ssl/
vim /etc/vsftpd/vsftpd.conf
</code></pre>
<pre><code class="language-ini">ssl_enable=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=TRUE
ssl_sslv2=FALSE
ssl_sslv3=FALSE
require_ssl_reuse=NO
ssl_ciphers=HIGH
rsa_cert_file=/etc/vsftpd/.ssl/vsftpd.pem
rsa_private_key_file=/etc/vsftpd/.ssl/vsftpd.pem

listen=YES
listen_ipv6=NO

pasv_enable=YES
pasv_max_port=30010
pasv_min_port=30000
</code></pre>
<p>重启服务使 SSL 配置生效：</p>
<pre><code class="language-bash">systemctl restart vsftpd
</code></pre>
<h3>3、创建受限 FTP 用户</h3>
<p>创建用户 <code>kylinftp</code>，密码 <code>Kftp@123</code>，限制其只能访问主目录：</p>
<pre><code class="language-bash">useradd -s /sbin/nologin -d /home/kylinftp kylinftp
passwd kylinftp
vim /etc/vsftpd/vsftpd.conf
</code></pre>
<pre><code class="language-ini">chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list
allow_writeable_chroot=YES
</code></pre>
<pre><code class="language-bash">touch /etc/vsftpd/chroot_list
vim /etc/pam.d/vsftpd
</code></pre>
<blockquote>
<p>将 <code>/etc/pam.d/vsftpd</code> 中的 <code>auth required pam_shells.so</code> 一行注释掉，避免 <code>nologin</code> 用户无法登录。</p>
</blockquote>
<p>重启服务：</p>
<pre><code class="language-bash">systemctl restart vsftpd
</code></pre>
<h3>4、启用双份日志</h3>
<p>配置 vsftpd 同时记录 <code>xferlog</code> 与 <code>vsftpd.log</code> 两份日志：</p>
<pre><code class="language-bash">vim /etc/vsftpd/vsftpd.conf
</code></pre>
<pre><code class="language-ini">xferlog_file=/var/log/xferlog
dual_log_enable=YES
</code></pre>
<pre><code class="language-bash">systemctl restart vsftpd
</code></pre>
<h2>八、chrony 时间同步</h2>
<p>配置 chrony，允许内网网段同步时间，并设置本地 stratum：</p>
<pre><code class="language-bash">vim /etc/chrony.conf
</code></pre>
<pre><code class="language-ini">allow 192.168.100.0/24
local stratum 10
</code></pre>
<p>重启并启用服务，查看同步状态：</p>
<pre><code class="language-bash">systemctl restart chronyd
systemctl enable chronyd
chronyc tracking
</code></pre>
<h2>九、DNS 正向解析</h2>
<p>配置 DNS 服务，按照网络地址规划对主机名做正向解析。</p>
<p>修改监听地址与查询权限：</p>
<pre><code class="language-bash">sed -i '/listen-on port 53/s/127.0.0.1/172.16.X.100/' /etc/named.conf
sed -i '/allow-query/s/localhost/any/' /etc/named.conf
</code></pre>
<p>添加正向区域：</p>
<pre><code class="language-bash">cat &gt; /etc/named.rfc1912.zones &lt;&lt;EOF
zone "kylin2025.com" IN {
	type master;
	file "kylin2025.com.zone";
};
EOF
</code></pre>
<p>创建区域数据文件：</p>
<pre><code class="language-bash">cat &gt; /var/named/kylin2025.com.zone &lt;&lt; 'EOF'
$TTL 1D
@	IN SOA	kylin2025.com. rname.invalid. (
					2025101101	; serial
					1D	; refresh
					1H	; retry
					1W	; expire
					3H )	; minimum
	NS	ns.kylin2025.com.

ns	A	192.168.100.100
server	A	192.168.100.100
client	A	192.168.100.200
EOF
</code></pre>
<p>最后重启 DNS 服务并验证解析：</p>
<pre><code class="language-bash">systemctl restart named
nslookup server.kylin2025.com
</code></pre>
<h2>十、所需镜像资源</h2>
<p>镜像资源可联系博主或前往官网自取。</p>
]]></content:encoded></item></channel></rss>