Java 这门语言与生俱来的显著特性就是“一次编译，到处运行”，这种功能得益于 JVM 平台的支持，Java 程序通常通过将其打包为 JAR 或 WAR 包，并依赖 JVM 和 Servlet 容器来运行。其底层运行时 JVM 采用 JIT（即时编译）模式来执行程序代码，JVM 会在运行时进行编译优化和动态执行代码，这通常会导致较高的内存占用。这样的好处是采用 JIT 可以热更新和热部署程序，并且 JVM 可以在运行期间对程序进行动态分析，来实时优化程序以达到最好的性能状态。Cloud Native 面临的问题随着云计算的发展，服务端程序的运行和部署方式发生了巨大变化，特别是在云原生（Cloud Native）场景下，服务器程序被打包成一个个容器运行。在这种环境下，传统方式容器化的 Java 程序面临着打包后的镜像文件过大的问题。镜像文件中需要包含 JRE 和各种第三方库，导致镜像体积庞大，占用更多的磁盘空间。目前云计算的计费方式也发生了改变，采用按需分配的实时计费方式，根据程序的内存占用和运行时间计费。这与传统 IDC 自托管式机房中长时间运行服务器程序形成鲜明对比，按需分配的计费方式显然更具性价比，因此优化 Java 程序在容器环境中的运行方式变得尤为重要。Serverless 面临的问题传统的 JVM 工作原理要求程序员先将编写的 Java 程序打包为 JAR 或 WAR 包，这是普通 Java 开发者所能接触到的程序编译和构建阶段。然而当程序运行起来之后，底层的 JVM 会进行许多复杂的操作，包括内存管理、垃圾回收、即时编译等。需要先启动 JRE 中的 JVM 来加载 Tomcat 服务器所需的字节码，再加载 WAR 包中的字节码文件到 Servlet 容器中运行服务，这个过程非常耗时。下图为整个传统 Java 程序从源代码到程序运行各个阶段的工作流程：下图为传统的 Java 运行时执行程序的生命周期，首先应用的开发者将 *.java 源代码编译为 bytecode 和 jar 依赖包通过 maven 构建打包为一个单独的 jar 包。当执行 java -jar xxx.jar 命令时，首先要启动 JRE 中的 JVM 程序，因为 JVM 本身也是一款单独程序对应着 $JAVA_HOME\bin 目录中的 java ，当 JVM 启动之后会在系统中创建一个进程，将要运行的 jar 程序包和 JRE 标准库中 class 文件加载入 JVM 内存中，并且找到 main 方法开始解释执行。当程序运行一段时间之后 JVM 充分获取了 Java 程序的一些执行数据信息，开始对程序进行 JIT 及时编译优化程序，当程序被 JIT 优化之后的程序才能达到和 C/C++ 这种直接编译为 Native Code 程序的性能。在云计算推崇的 Serverless 架构场景下，Java 的 JIT 模式并不适合，Serverless 应用程序需要能够快速启动以响应事件驱动的请求，并且在空闲时自动关机销毁，释放在服务请求期间所使用的内存。然而，这种需求与传统的 Java 程序运行模式背道而驰，使得 Java 在 Serverless 场景中显得不适用，这就是被业界称为的 冷启动 问题。为了解决这一痛点，Oracle Labs 推出一款新的 JDK 产品 GraalVM JDK ，相比传统的 JDK 该产品内置 Java 静态编译工具和一款新的虚拟机，和传统的 JVM 不一样 GraalVM 是支持多语言进行混合编程的虚拟机，这也是它一大亮点。GraalVM 中的静态编译工具不仅是支持将 Java 编译为二进制可执行文件，还支持 JavaScript 和 Python 等其他语言的编译优化功能，本文只关注于 GraalVM 对 Java 程序进行 AOT（Ahead-Of-Time）编译的解决方案。使用 GraalVM 对 Java 程序进行 AOT 编译的解决方案，可以显著缩短启动时间并降低内存占用，使 Java 程序更适合在 Serverless 环境中运行，通过 AOT 编译，编译器生成一个 Native Code 二进制文件，即对应平台架构的机器代码程序。这种方式无需像 JIT 那样依赖于 JVM 运行，在内存和磁盘占用上有显著改善。下图为 AOT 编译工作流程，在编译阶段会程序进行静态分析，通过它内置工具分析 Java 源代码中依赖关系，将其所有依赖和代码执行逻辑都进行提前编译为机器代码，缺点也很明显可能会失去在 JIT 模式下的动态反射功能。如上图，在 AOT 模式下将在运行时的过程放到在程序构建阶段，构建阶段会对 Java 程序执行静态代码分析和依赖可达性分析，将其所有的依赖软件包都编译生产所属平台对应的可执行二进制文件。AOT 编译优化对程序进行 AOT 编译特别适合云应用程序，被 AOT 编译优化之后的程序启动速度足够快，从而缩短启动时间并更直接地水平扩展云服务，内存占用相比 JVM 模式下要少很多，这对于在云中运行的容器初始化的微服务尤其有益。AOT 前提是完全封闭的运行空间，因为它消除了各种代码注入的可能性，例如 2021 年震惊网络的 Log4j 漏洞就是由于利用了 Java 中的动态类加载机制而发生的，在 AOT 编译之后程序就不会发生类似于漏洞。下面这张蜘蛛图详细展示了不同程序运行方式的区别：介于 Java 程序部署平台和主流的容器技术都是基于 Linux 系统，所以这里以 Ubuntu 发行版本作为基础 AOT 编译和运行打包环境。要对 Java 程序进行 AOT 编译的话首先要安装 GraalVM JDK 的发行版本，这个可以去它的官方网站 graalvm.org 上下载和安装，安装步骤和传统基于 OpenJDK 的发行版本步骤一致。在 Linux 中要是使用 GraalVM 提供的 AOT 静态编译功能，首先需要在操作系统中安装一些 C/C++ 编译器的工具链，在 Ubuntu 和其他基于 Debian 的系统中执行下面命令：sudo apt-get install build-essential libz-dev zlib1g-dev其中的 build-essential 是一个用于 C 语言的开发包，包含了 gcc make gdb 和 libc 函数库等很多工具，这些工具和库对于编译 C 和 C++ 程序是必不可少的。libz-dev 和 zlib1g-dev 是用于压缩和解压缩的开发库，这两个软件包提供了开发 zlib 库的头文件和开发工具，允许您在编写 C/C++ 程序时链接和使用 zlib 压缩库，GraalVM 的 AOT 编译依赖于这些工具来构建本地映像。默认情况下，GraalVM 的 AOT 编译在链接时通常使用系统的标准 glibc 库，但在小型嵌入式设备或对内存要求极低的环境中，glibc 并未针对这些场景做出特别优化。相比之下目前有另外一种专有的 musl 标准库实现，musl 被开发出来就是专注在内存受限的设备上表现优异，使用 musl 生产的二进制文件通常比使用 glibc 生产的文件体积更小，所以更推荐直接使用 musl 替代 glibc 去做编译链接优化。在 GraalVM 的 AOT 编译工具中提供自定义选项，允许开发者去自定义使用 musl 去做生成二进制文件优化。部分 Linux 发行版本系统可能没有内置 musl 库需要提前安装，通过源代码安装的方式可以去 musl.libc.org 官方网站下载源代码包编译安装。但这种方式的需要自己手动配置和编译步骤比较繁琐，建议使用下面这种方式，通过别人已经编译好的 x64 位的 x86_64-linux-musl-native 程序，下载压缩包到指定位置就可以使用。基于 musl 编译的前置条件是依赖于 musl 工具链，但在 GraalVM JDK 中并未提供相关的工具链的支持，需要提前下载 x86_64-linux-musl-native 和 zlib 文件，创建一个用于存放 musl 工具链的目录，将其解压到这个目录中，并且将 x86_64-linux-musl-gcc 和 zlib 进行整合编译安装，步骤如下：# 创建一个工具存储目录 mkdir /usr/local/graal-aot-tools # 进入到这个目录 cd /usr/local/graal-aot-tools # 下载 x86_64-linux-musl 工具链 wget http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz # 解压下载到的 musl 工具链包 tar -zxvf x86_64-linux-musl-native.tgz # 复制文件到工作根目录 cp -r x86_64-linux-musl-native/* . # 下载 zlib 依赖 wget https://zlib.net/current/zlib.tar.gz # 解压 zlib 依赖源码包 tar -zxvf zlib.tar.gz通过一条特定 TOOLCHAIN_DIR 环境变量来指定 musl 工具链安装路径，graalvm 的 aot 编译器会通过此变量来找到 x86_64-linux-musl 的位置：export TOOLCHAIN_DIR="/usr/local/graal-aot-tools" export PATH="$TOOLCHAIN_DIR/bin:$PATH" export CC="$TOOLCHAIN_DIR/bin/gcc"最后把 zlib 和 TOOLCHAIN_DIR 整合进行编译安装，进入 zlib 源代码安装目录中执行： ./configure --prefix=$TOOLCHAIN_DIR --static make make install配置完成之后可以通过 x86_64-linux-musl-gcc 命令检查 musl 工具链是否安装成功。如果使用的是 maven 来构建自动化项目，可以使用 pom 文件中配置 AOT 相关构建参数，来自动化来构建二进制文件： <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>${native.maven.plugin.version}</version> <extensions>true</extensions> <executions> <execution> <id>build-native</id> <goals> <goal>build</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <skip>false</skip> <imageName>${bin.file.name}</imageName> <mainClass>${app.main.class}</mainClass> <buildArgs> <buildArg>--gc=G1</buildArg> <buildArg>--static</buildArg> <buildArg>--libc=musl</buildArg> <buildArg>--no-fallback</buildArg> <buildArg>-march=compatibility</buildArg> </buildArgs> </configuration> </plugin>在项目的根目录中执行 mvn native:build 命令就可以调用位于 $JAVA_HOME/bin/native-image 工具执行静态分析并且生成二进制可执行文件。AOT 编译对计算机硬件资源的要求非常高，取决于项目的大小，大型项目编译时需要占用很多 GB 的内存和大量的 CPU 使用率，不过这会使用高性能的 CI/CD 服务器来完成自动化构建工作。通过 x86_64-linux-musl-gcc 和 musl 做链接编译生成的二进制可执行文件，完全使用的是静态链接的方式，从而提高了可移植性。在不同的 x86_64 的 Linux 系统上运行这些静态链接的二进制文件时，不需要任何额外的库，这对于部署和分发二进制文件非常有用。从编译日志输出可以看到，AOT 编译器启用了 PGO: ML-inferred 优化，这个默认在其他平台是不会启动的，只要在 Linux 平台下使用 x86_64-linux-musl-gcc 才会开启的，由于使用的完全静态链接的二进制文件，所以可以使用 upx 针对这个二进制文件进行压缩，压缩之后的二进制文件可以小到 3 MB 大小。这对于将其打包为 docker 容器镜像文件非常有利，意味着不需要任何其他基础镜像，直接打包可执行二进制文件放到容器中执行，基于 scratch 基础镜像打包之后镜像大小和二进制文件大小一致。# 使用一个轻量的基础镜像 FROM scratch # 复制静态链接的二进制文件到根目录 COPY target/vasedb / # 设定工作目录，这是可选的 WORKDIR / # 默认的启动命令 CMD ["/vasedb"]采用 AOT 和 JIT 模式通过这些数据，形成鲜明的对比，优势也展示出来了，以往采用 JIT 模式运行的 Java 被容器化之后镜像大小往往都是上百 MB 大小的。个人认为有 Serverless 更激进优化方案，在 AOT 编译之后程序会内嵌入一个微型的 SubstrateVM 运行时，这个运行时负责 Java 程序在运行阶段一些多线程和内存分配、垃圾回收功能，默认 AOT 编译使用的 serial 垃圾回收器是一种单线程垃圾回收器，适用于单线程应用或有少量线程的应用，另外一个可选的垃圾回收器是 G1 垃圾回收器，G1 用于多核处理器和大内存机器的高性能垃圾回收器。但是在 Serverless 应用场景中往往应用程序运行时间很短，在程序在运行一段时间之后就主动销毁了，这有点类似于在 AOT 编译阶段的编译程序本身步骤，当编译器编译完程序就销毁了，是一种一次性应用程序。熟悉 JVM 多个垃圾回收器实现应该知道其中有一个叫 Epsilon 垃圾回收器，该垃圾回收器不会执行任何垃圾回收操作，它只进行内存分配而不释放内存。所以我个人认为在某些特定的 Serverless 应用场景中完全可以使用 Epsilon 内存分配器的方案，因为部分场景程序运行时长还没有等到 GC 回收器触发的时间点就被底层基础设施销毁了，程序运行所分配的内存会被 OS 主动回收，而不需要执行垃圾回收器进行回收，去掉 SubstrateVM 中的垃圾回收器部分，打包应用程序体积会更小。引用资料文件Oracle (The Art of) (Java) Benchmarking PPTAOT vs. JIT Compilation in JavaGraalVM Native Image 快速入门
如果是一名服务端程序员，开发最多程序应该为服务器端应用程序，而服务器端端应用程序大部分部署在 Linux 操作系统之上，在 Linux 上部署应用和管理 Linux 系统大部分都是使用 SSH 端方式管理。这篇文章要介绍的是 Ansible 自动化运行工具，它可以帮助我们自动化各种 IT 任务，从部署应用程序和服务到协调网络设备和云资源的管理，个人感觉相比 K8s 的设计 Ansible 更贴合的是物理机器设计的管理软件。[scode type="blue"]Ansible 是基于代理的，不需要在被管理的节点上安装客户端直接使用 SSH 协议进行通信，而 Kubernetes 是基于主从节点的，需要在被管理的节点上安装 Kubernetes Agent ，Ansible 主要用于管理配置和协调各种 IT 任务，包括应用程序部署、系统配置、网络管理等，Kubernetes 是一种容器编排平台，用于自动化和管理容器化应用程序的部署、伸缩、故障恢复和负载均衡。[/scode]上图为 ansible 的软件架构图。安装 AnsibleAnsible 目前支持类 RHEL 发行版本系列系统，大部分默认的系统镜像软件默认是不带有 ansible 服务程序，需要自己手动安装，需要从 Extra Packages for Enterprise Linux 扩展软件仓库中获取，首先要配置 epel 软件仓库最新的地址，执行下面命令：sudo yum install -y epel-release # 安装 ansible 服务软件 yum install -y ansible # 检查是否成功安装 ansible --version[scode type="yellow"]通过上面 2 条命令即可安装 Ansible 服务程序，其他受管理的节点不需要安装 Ansible ，但是依赖于 OpenSSH 程序，集群中的其他节点必须提前安装好 OpenSSH 和 Python3 运行环境并且对外提供 SSH 连接服务，目前 Ansible 可以支持千台机器的管理和运维支持，默认的安装配置文件位于在 /etc/ansible/ansible.cfg 文件中。[/scode]参数描述-i, --inventory-file指定主机清单文件路径-l, --limit指定要操作的主机或主机组-u, --user指定 SSH 用户名-b, --become切换为管理员权限-K, --ask-become-pass请求管理员密码-c, --connection指定连接类型，如 SSH 或本地连接-t, --timeout指定操作超时时间-k, --ask-pass请求 SSH 密码-s, --sudo使用 sudo 命令执行任务-f, --forks指定同时运行任务的最大数量-v, --verbose显示详细输出信息-h, --help显示帮助信息以上是 ansible 常用的主要命令行参数信息。初始化集群首先 Ansible 是基于 SSH 协议进行通信的，我们必须将管理节点的 SSH 公钥传送到被控制子节点中，从而实现免密码登录到远程其他子节点主机，首先要创建 SSH 密钥对，执行下面命令：# 生成 ssh 密钥对 ssh-keygen -t rsa -b 2048 -C "管理员邮箱地址" # 复制公钥到远程子主机上 ssh-copy-id 192.168.31.86[scode type="yellow"]生成默认公钥地址位 /root/.ssh/id_rsa.pub ，这个文件的中内容就为公钥的字符串，也可以手动上传到子节点服务器 ~/.ssh/authorized_keys 文件中。如果需要管理的机器超过几百台，每次都手动复制密钥文件很麻烦，可以编写一个 shell 脚本来帮助复制公钥文件到远程主机中：[/scode]#!/bin/bash # 设置公钥路径和远程主机列表文件的路径 public_key_path="/root/.ssh/id_rsa.pub" remote_hosts_file="/remote/hosts.txt" # 读取远程主机列表 remote_hosts=$(cat "$remote_hosts_file") # 循环遍历远程主机并将公钥复制到其中 for host in $remote_hosts; do # 将公钥复制到远程主机 ssh-copy-id -i "$public_key_path" "$host" <<EOF your_password EOF done上面编写了一段代码，将 /root/.ssh/id_rsa.pub 文件逐个复制到 /remote/hosts.txt 文件中的主机中，hosts.txt 文件里面内容为主机 IP 地址信息，内容如下：192.168.31.1 192.168.31.2 node1.jdk.world这样一来，复制密钥的操作就完成自动化了，几千台主机操作也没有问题。资产管理Ansible 服务程序将所有受管理的节点称之为 资产 ，有静态资产和动态资产之分，静态资产可以认为是将远程受管理的的节点 IP 写入一个固定的文件中保存，而动态资产可以在运行过程中动态从远程服务器或者数据库中加载。# 手动管理，在指定的机器运行 ping 模块，并且收集结果 ansible all -i 192.168.31.171,node1.jdk.world -m ping[scode type="yellow"]由于管理的机器资源比较多，不可能全部手动填写和配置，ansible 为了方便管理提供自定义资源方式，通过名为 inventory.ini 文件来定义需要管理的资源主机，也可以在 /etc/ansible/hosts 文件中编写，下面是一些例子：[/scode]# inventory.ini 文件例子 [webservers] web1 ansible_host=172.16.19.132 ansible_user=root ansible_password=123456 [dbservers] db1 ansible_host=192.168.31.96 ansible_user=root ansible_password=123456 db2 ansible_host=192.168.31.213 ansible_user=root ansible_password=123456上面定义两组服务器类型 webservers 和 dbservers ，每个组都包含一个或多个主机，对于每个主机使用了 ansible_host 、ansible_user 和 ansible_password 变量来定义这些连接信息，此种方式存在很大弊端，密码被明文存储在文件中，如果意外泄露导致机器账号和密码泄露。[scode type="blue"]对于要 inventory.ini 文件如果要执行的可以使用 ansible all -i inventory.ini -m ping 这种做法和手动填写主机信息一致，all 参数可以换成配置文件中的子组名。[/scode]# 对所有子主机执行 ping 模块 ansible all -i inventory.ini -m ping # 安装分组来执行 ping 模块 ansible dbservers -i inventory.ini -m ping # 通过结构化输出展示机器信息 ansible-inventory --graph # 查看节点信息列表 ansible all --list-hosts[scode type="blue"]ansible 配置文件中支持的参数变量列表有：[/scode]变量作用ansible_ssh_host受管理主机名ansible_ssh_port端口号ansible_ssh_user默认账号ansible_ssh_pass默认密码ansible_shell_type默认 shell 类型[scode type="yellow"]如果所有主机默认的账号和密码一致的话可以简写，定义成全局变量的方式进行，并且还可以将主机分类，多个子类可以组成另外一个主机类：[/scode][web] 172.16.19.132 [db] 192.168.31.96 192.168.31.213 # 其他子组，组成的组 [all:children] web db # 全局变量 [all:vars] ansible_user=root ansible_password=123456对于保存好的资产文件，在操作的时候可以根据类型对资源进行控制和操作，这个过程称之为 资源选择 ：# 全部的服务器 ansible all -i inventory.ini -m ping # 针对于 web 服务器组 ansible web -i inventory.ini -m ping # 针对于 db 服务器组 ansible db -i inventory.ini -m ping模块系统Ansible 功能的多样性就取决于它设计的模块系统，模块可以认为是一个功能的集合，不同的模块可以实现不同功能。默认 ansible 有一些基础的核心模块，也有扩展模块，另外还可以用户自定义模块，对应的是用户自定义功能实现。如果需要查看内部支持的模块说明可以使用 ansible-doc -l 命令可以列出所有的模块信息，如果要查看某个特定的模块信息可以使用 ansible-doc module_name 命令。[scode type="green"]使用模块功能执行操作命令为 ansible 资产范围 -i 资产文件 -m 模块名称 [-a 模块参数] ，下面列举了一些常用的 ansible 内置的模块：[/scode]模块名称描述command在目标主机上执行指定的命令或脚本。shell在目标主机上以 shell 方式执行指定的命令或脚本。copy将本地文件或目录复制到目标主机。file管理目标主机上的文件和目录，包括创建、删除、修改、更改权限等操作。service管理目标主机上的服务，包括启动、停止、重启等操作。apt/yum/pacman管理目标主机上的包，包括安装、升级、删除等操作。user/group管理目标主机上的用户和组，包括创建、删除、修改等操作。template将本地的 Jinja2 模板渲染成目标主机上的文件。debug在执行 Ansible 任务时输出调试信息。wait_for等待目标主机上的某个条件成立，例如等待某个端口开放或某个文件存在。shell直接执行用户指定的名称支持特殊字符串filesystem格式化硬盘设备文件mount挂载硬盘设备文件firewalld管理主机上的防火墙程序get_url从网络中下载文件cron添加计划任务信息[scode type="yellow"]例如使用 firewalld 模块为主机设置防火墙规则，在 ansible 中使用 firewalld 模块，可以方便地管理 Linux 上的防火墙规则：[/scode]# 添加端口到 web 资产主机上 [root@cs9 ~]# ansible web -i inventory02.ini -m firewalld -a "port=8080/tcp permanent=true state=enabled immediate=true" 172.16.19.132 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": true, "msg": "Permanent and Non-Permanent(immediate) operation, Changed port 8080/tcp to enabled" } # 立即实现防火墙 [root@cs9 ~]# ansible web -i inventory02.ini -m command -a "firewall-cmd --reload" 172.16.19.132 | CHANGED | rc=0 >> success[scode type="yellow"]不光可以使用 -a 参数传递变量到被控主机上，-a 参数还支持从资产文件中读取预先定义好的变量来执行，例如有资源文件内容为：[/scode][web] 172.16.19.132 [db] 192.168.31.96 192.168.31.213 # 其他子组，组成的组 [all:children] web db # 全局变量 [all:vars] ansible_user=root ansible_password=123456 # 变量定义 [web:vars] http_port=8080这样就可以使用 -a 参数来获取变量的值了：# 获取预定义的变量 http_port [root@cs9 ~]# ansible web -i inventory03.ini -a 'echo "{{http_port}}"' 172.16.19.132 | CHANGED | rc=0 >> 8080-a 参数类似于 -m shell ，类似于默认的 shell 模块，可以不需要添加 shell 模块，默认 -a 即可。PlayBook 文件[scode type="blue"]上面都是介绍的基于命令行下的多节点管理和控制，默认这种情况针对简单需求是可以满足，如果多条 shell 命令和更复杂的操作默认的 Ad-hoc 模式是满足不了，ansible 另外一种模式为 playbook 模式，允许管理员提前编写好剧本文件，通过剧本文件的内容来管理和控制多节点。[/scode]在 ansible 中的 Playbook 文件的格式为 yaml ，必须严格按照 yaml 格式才能被正常解析和运行，命名为 playbook.yml 或者其他你喜欢的名称，用于定义 ansible 任务的执行步骤、主机和变量等信息，可以帮助自动化部署、配置和管理远程主机。--- - hosts: webserver # 针对哪些资产启作用，多个服务器名称可以使用逗号隔开 vars: # 定义变量 http_port: 80 tasks: # 执行任务计划集合 - name: Install Apache # 任务的名称 yum: # 任务所使用的模块，yum 软件管理 name: httpd state: present - name: Start Apache service: name: httpd state: started enabled: true[scode type="yellow"]执行 playbook 之需要使用 ansible-playbook 文件路径.yaml 即可，如果 playbook 任务执行成功会有 ok 和 change 字段信息提示。[/scode]Role 规则在 ansible 通过角色功能，这里角色是指定的是一些功能可以复用，单独抽象成为一个角色类模块化，简单来说就把一些常用的功能模块化在需要使用的时候可以加载，类似于编程中函数封装实现类似，分装成为可复用的对象。[scode type="yellow"]目前 ansible 角色获取有 3 种方式为：加载系统内置角色 、外部环境获取例如使用 Ansible Galaxy 、自定义创建角色，不同的角色能做的事情就不一样，通过角色名称来分类处理任务。在使用 playbook 编写复杂的任务时，会出现很大变量和一些配置规则导致整个 playbook 文件过于复杂，角色存在的目的就是要将 playbook 中的 Variables 和 Handlers 、 tasks 单独抽象出来，达到组建之间复用的设计，一个完整的角色目录结构如下：[/scode]my_role/ ├── README.md # 角色的说明文档 ├── defaults/ # 默认变量 │ └── main.yml ├── files/ # 需要在远程主机上复制的文件 ├── handlers/ # 处理器 │ └── main.yml ├── meta/ # 元数据 │ └── main.yml ├── tasks/ # 任务 │ ├── install.yml # 安装软件包 │ ├── config.yml # 配置文件 │ └── service.yml # 启动服务 ├── templates/ # 模板文件 ├── tests/ # 测试用例 │ ├── inventory # 测试的主机清单 │ └── test.yml # 测试任务 └── vars/ # 变量 └── main.yml[scode type="green"]可以直接使用 ansible-galaxy init 角色名称 进行创建，下面使用手动创建一个安装和部署 tomcat 的例子，首先基于上面的标准目录结构，来创建一个用于安装 tomcat 的角色目录，代码如下：[/scode]#!/bin/bash # 创建目录结构 mkdir -p roles/tomcat/{tasks,templates,vars,files,defaults,meta} # 循环创建目录中的文件 for d in `ls roles/tomcat/` do touch ${d}/main.yml done[scode type="yellow"]首先我们要创建一个安装任务，以往使用的 playbook 文件任务和变量全部写到一个文件中，而现在使用的自定义角色方式，执行任务单独写入到 task.yml 中，打开文件 roles/tomcat/tasks/main.yml ：[/scode]- name: 安装Java yum: name: "{{ java_package_name }}" state: present - name: 下载Tomcat get_url: url: "{{ tomcat_download_url }}" dest: "/opt/apache-tomcat-{{ tomcat_version }}.tar.gz" - name: 解压Tomcat unarchive: src: "/opt/apache-tomcat-{{ tomcat_version }}.tar.gz" dest: /opt/ remote_src: yes creates: "/opt/apache-tomcat-{{ tomcat_version }}" - name: 配置Tomcat template: src: tomcat.service.j2 dest: /etc/systemd/system/tomcat.service - name: 设置Tomcat权限 file: path: "/opt/apache-tomcat-{{ tomcat_version }}" mode: 0755 state: directory recurse: yes - name: 启动Tomcat systemd: name: tomcat state: started enabled: yes - name: 添加8080端口 firewalld: port: 8080/tcp permanent: yes state: enabled immediate: yes[scode type="yellow"]上面使用了多个模块：get_url 下载网络文件，unarchive 压缩文件处理模块，template 模版文件模块，file 文件模块，systemd 系统进程管理模块，firewalld 防火墙模块，如果不知道如何编写可以使用 ansible-doc 模块名称 来查看对应模块的示例。剩下的就是要对 task.yml 文件中每个步骤中所依赖文件，继续编写规则，这里使用到了 systemd 模块，需要依赖 service 文件，所以通过 template 的方式编写一个 systemd 管理配置文件，需要创建一个 /templates/tomcat.service.j2 文件[/scode][Unit] Description=Tomcat After=syslog.target network.target [Service] Type=forking Environment=CATALINA_PID=/opt/apache-tomcat-9.0.73/temp/tomcat.pid Environment=CATALINA_HOME=/opt/apache-tomcat-9.0.73 Environment=CATALINA_BASE=/opt/apache-tomcat-9.0.73 ExecStart=/opt/apache-tomcat-9.0.73/bin/startup.sh ExecStop=/opt/apache-tomcat-9.0.73/bin/shutdown.sh Restart=on-failure [Install] WantedBy=multi-user.targe[scode type="yellow"]剩下的为变量参数文件在 vars 目录中，而 defaults 文件是变量值的默认变量文件，用于定义默认变量值，这些变量的值可以被其他的变量文件覆盖，如果没有设置变量或者修改变量，则使用 defaults 文件中预先定义的变量，defaults 内容如下：[/scode]# 默认端口 tomcat_port: 8080[scode type="yellow"]剩下的 vars 中的变量是根据目前需求和运行情况所要设置的变量，可以更改的变量，常规变量文件，用于定义在角色或任务中使用的常规变量，vars 文件中定义的变量值将覆盖 defaults 文件中的值，例如定义 tomcat 下载地址和 jdk 版本信息：[/scode]java_package_name: "java-1.8.0-openjdk-devel" tomcat_version: "9.0.73" tomcat_download_url: "https://downloads.apache.org/tomcat/tomcat-9/v{{ tomcat_version }}/bin/apache-tomcat-{{ tomcat_version }}.tar.gz"[scode type="green"]最为关键的一步如何在指定的资源上执行对应的角色任务，还是通过 playbook 的方式，只不过要编写一个 playbook 入口文件，例如：[/scode]--- # 在 web 资源上执行 tomcat 角色 - name: 安装 tomcat 角色 hosts: web roles: - tomcat[scode type="green"]最后在对应的资源上执行对应角色完成对应自动化任务，下面为 tomcat 角色执行结果。[/scode][scode type="yellow"]自己编写角色很麻烦，并且日常我们开发使用运维的常用的软件也就那么几个软件，所以官方有一个叫 Ansible Galaxy 站点允许开发者上传自己的角色模块，方便其他下载复用，类似于 Java 中的 Maven，wasmer 的 wapm.io 的一样，可以复用别人的编写角色，如下为 mysql 角色：[/scode]分享 Role自定义编写的 roles 也可以通过 tar 命令打包成一个 tar 包，上传到互联网上，只要能被正常访问到就能被下载使用，下面为一个例子：# 打包刚刚自己编写的规则 tar -czvf ansible-tomcat-roles.tar.gz /etc/ansible/roles/tomcat/ # 下载文件 wget https://repo.ibyte.me/ansible/ansible-tomcat-roles.tar.gz # 解压缩文件 tar -czvf ansible-tomcat-roles.tar.gz -C /etc/ansible/roles/[scode type="yellow"]以上是手动下载解压的方式，放到对应目录中和本地角色使用没有如何区别，但是比较繁琐，可以直接在 remote.yml 中直接编写远程地址，ansible 自动下载和执行任务，如下：[/scode]--- # 指定下载源 - name: 下载 ansible-tomcat-roles.tar.gz src: https://repo.ibyte.me/ansible/ansible-tomcat-roles.tar.gz name: ansible-tomcat-roles最后通过 ansibe-galaxy install -r remote.yml 来安装。其他资料Dynamic Azure Automation with AnsibleAnsible「2.9」 中文官方文档
前面的技术博文解释过通过 nmcil 来配置网络参数，而这篇文章要介绍的是 DHCP 协议程序，每当我们电脑接入网络时都会自动分配 IP 和 DNS 、子网掩码信息，并没有自己来配置网络连接信息，而是 IP 自动分配，能做到这一点就得益于 DHCP 协议，能帮助我们的设备自动配置网络信息。[scode type="blue"]现在建设你作为学校机房的管理员或者是网吧的管理员，每次都有不同的网络设备要接入到就局域网中，这就会带来大量配置网络信息工作，而且每次上完课部分设备又离开了教室，分配出去的 IP 如果不能回收，会导致 IP 地址被占用，不能被其他人再次使用，导致 IP 不够用浪费 IP 情况，也会带来手动管理 IP 麻烦，DHCP 要解决的问题就是针对这些场景的，自动化配置和自动化管理接入设备的 IP 地址。[/scode]DHCP 协议目前大部分网络设备和操作系统都默认支持 DHCP 协议，如果系统没有支持可以通过软件包的方式安装 DHCP 软件来支持 DHCP 协议，DHCP 分为两个部分一个服务器另外一个是客户端，服务器为提供网络接入服务的设备，而客户端则是要接入网络的设备。[scode type="yellow"]配置 DHCP 服务器：首先，您需要在网络上设置一个 DHCP 服务器，并为 DHCP 服务器分配 IP 地址和其他必要的网络参数，您可以使用现有的服务器，如路由器或专用 DHC P服务器，也可以使用开源 DHCP 服务器软件，如 ISC DHCP 或 dnsmasq ，在 Linux 下安装 dhcp 服务器可以使用下面命令：[/scode]# 安装 dhcp 服务器 dnf install -y dhcp-server 配置文件位如 /etc/dhcp/dhcpd.conf 文件中，DHCP 选项可以用来设置其他网络参数，如 DNS 服务器地址、NTP 服务器地址和 SMTP 服务器地址等，可以在 DHCP 服务器上配置这些选项，并在 DHCP 客户端上使用它们，有客户端接入到网络中 DHCP 会按照这些参数来配置自己的网络参数，官方提供参数参考文件，目录位于 /usr/share/doc/dhcp-server/ ，目前支持的网络参数列表：配置项配置值option domain-nameexample.comoption domain-name-serversns1.example.com, ns2.example.comsubnet192.168.1.0netmask255.255.255.0range192.168.1.100 - 192.168.1.200option routers192.168.1.1option subnet-mask255.255.255.0option broadcast-address192.168.1.255default-lease-time600max-lease-time7200hostclient1hardware ethernet00:11:22:33:44:55fixed-address192.168.1.10class"phone"matchsubstring(hardware, 1, 3) = 00:11:22option domain-name-serversns3.example.comshared-network"office"subnet192.168.2.0netmask255.255.255.0range192.168.2.100 - 192.168.2.200option routers192.168.2[scode type="yellow"]最常用参数也就那么几个，下面整理参数对应的作用说明：[/scode]配置参数说明option domain-name指定 DHCP 服务器分配的主机名所属的域名。option domain-name-servers指定 DHCP 客户端应该使用的 DNS 服务器列表。subnet定义 DHCP 服务器分配 IP 地址的子网。netmask定义 DHCP 子网的子网掩码。range定义 DHCP 服务器可以分配的 IP 地址范围。option routers指定 DHCP 客户端应该使用的网关地址。option subnet-mask指定 DHCP 客户端应该使用的子网掩码。option broadcast-address指定 DHCP 客户端应该使用的广播地址。default-lease-time定义 DHCP 服务器分配的 IP 地址的默认租约时间（以秒为单位）。max-lease-time定义 DHCP 服务器分配的 IP 地址的最大租约时间（以秒为单位）。host指定 DHCP 客户端的主机名。hardware ethernet指定 DHCP 客户端的 MAC[scode type="yellow"]下面是一个简单的示例代码，配置 DHCP 服务器能分配的 IP 地址池数量就 50 个，并且前 24 位为网段，192.168.31.1 为默认网关地址，最后默认 IP 生命周期时间和最大续租时间，单位为秒：[/scode]# 设置 dns 服务不能自动动态更新 ddns-update-style none; # 忽略客户端更新 DNS 记录 igrone client-update; # 网段作用域 subnet 192.168.31.0 netmask 255.255.255.0 { range 192.168.31.50 192.168.31.150; option subnet-mask 255.255.255.0; option routers 192.168.31.1; option domain-name "ibyte.me"; option domain-name-servers 1.1.1.1; defualt-lease-time 21600; max-lease-time 43200; }[scode type="blue"]DHCP 自动分配在某些场景下也有一些特殊的问题，例如 DHCP 自动续约服务，自动回收 IP 地址服务，导致某些 IP 不能和某个特定设备绑定。为了解决这些问题 DHCP 新版本有一个类似于绑定特定设备的服务，会把某个特定的 IP 绑定到某个特定的设备至上，要做到这一点就得知道特定设备的 mac 地址，大部分情况下可以在网络设置中查询到 mac 地址，写入 DHCP 到服务端配置文件中，这样 DHCP 会预留某个 IP ，当特定的设备 mac 地址和配置文件中一致即分配给这个设备特定的 IP ，想要达到这一点只需要在 DHCP 服务器端配置文件中添加一段代码：[/scode]host www.ibyte.me { # 指定网卡 mac 地址 hardware ethernet 00:0A:02:0B:03:0C; # 指定固定 IP 分配 fixed-address 192.168.31.88; }通过此种方式配置的 IP 和 Mac 地址会相互绑定在一起，特定设备分配特定的 IP 地址。其他资料DHCP 与 ARP 又是什么？
关系型数据库目前有很多产品共同特点是分为商业版本和社区开源免费版本，目前社区开源版本的关系型数据库用的最多的数据库是 MySQL Community Server Edition ，最大特点为免费商业使用也不用顾虑任何法律相关的问题，MySQL 被 Oracle 公司收购之后也推出了商业服务，原有的 MySQL 开发人员为了能摆脱 Oracle 相关的控制也在 MySQL 原有代码的分支上开发出来了 MariaDB 开源社区驱动开发的版本。[scode type="lbule"]对于一个想要学习数据库技术的新手来说要解决的问题当然是如何拥有数据库服务器端，数据库也是一种 C/S 架构的程序，对于多数开发者来说用的最多的是客户端，服务器管理大部分由 dba 操作，这篇博客将介绍如何通过 tar 包的方式在 RHEL9 上安装 MySQL 服务端程序。[/scode]准备工作首先需要一台已经安装 RHEL9 的操作系统，目前 rhel 官方的仓库已经支持直接使用 dnf 命令安装 MySQL 服务器，这种针对是完全没有经验用户傻瓜安装方式。如果需要自定义安装路径和用户组还是推荐使用 tar 包的方式安装，首先安装一些依赖包和工具包：yum -y groupinstall "Development Tools" yum -y intsall vim wget libaio[scode type="yellow"]最后去 MySQL 官方网站找对应的 Linux Generic Archive 安装包下载到本地：[/scode]wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz[scode type="yellow"]创建一个用于存储 MySQL 服务器程序的目录，通过命令 mkdir -p /usr/local/mysql ，将其解压到这个目录中：[/scode]# 解压到指定目录中 tar -xvf mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz -C /usr/local # 重新命名目录 mv mysql-8.0.32-linux-glibc2.12-x86_64 mysql解压完成之后接下来需要创建两个目录，一个用来存放 mysql 运行时产生的日志文件，另外一个为存放 mysql 生成的数据文件目录，需要创建两个目录命令如下：# 创建 log 目录 和 创建数据目录 sudo mkdir -p /usr/local/mysql/data /usr/local/mysql/logs将 mysql 的支持文件拷贝到 /etc/systemd/system/ 目录中，该目录存放都是一些开机自动化启动程序，能够使用 systemctl 程序的子命令 start 、 stop 、restart 、reload 来管理某个具体的应用，例如这里的 mysql 程序。# 复制支持文件，并且重新命名为 mysqld sudo cp /usr/local/mysql/support-files/mysql.server /etc/systemd/system/ # 所有者应有执行权限 sudo chmod +x /etc/systemd/system/mysql.server[scode type="blue"]接下来要创建用来运行 mysqld 的用户，和将上面的文件夹所有者和所属组全部设置为新创建的 mysql 用户和组：[/scode]# 创建用户 useradd -M -s /sbin/nologin mysql # 更改文件所属组和所有者 chown -R mysql:mysql /usr/local/mysql[scode type="yellow"]最后将 mysql 的二进制命令工具目录加到环境变量中，以方便在任何地方都能使用 mysql 提供的命令工具：[/scode]# 在 /etc/profile 最后一行添加 echo "PATH=$PATH:/usr/local/mysql/bin" >> /etc/profile # 刷新环境变量 source /etc/profile到此整个安装前置条件已经满足，剩下就是对数据库进行安装和初始化了。初始化安装当准备工作完成之后就需要对数据库进行初始化，这里初始化使用 mysqld 命令，要提供 --initialize 参数，--user 指定运行程序用户，--basedir 指定数据库生成的数据文件存放的目录，--defaults-file 指定配置文件路径，执行命令会自动初始化数据库并且设置默认的 root 登录密码：[root@rhel9 ~]# sudo mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data 2023-03-22T12:08:36.476715Z 0 [System] [MY-013169] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.32) initializing of server in progress as process 23676 2023-03-22T12:08:36.486664Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2023-03-22T12:08:36.983513Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended. 2023-03-22T12:08:38.468726Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: %ajsN=2G_)nf[scode type="red"]如果是比较新的系统可能会出现 libtinfo.so.6.1 文件版本过高，导致 mysql 客户端链接的 libtinfo.so.5 ，所以创建一个软链接：[/scode]ln -s /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5[scode type="green"]最后使用 mysql 客户端登录连接到服务器，第一步为重置 mysql 到密码，一定要重新设置 root 的密码，否则不允许任何操作：[/scode]# 重置密码 alter user 'root'@'localhost' identified by 'xxxxx'; # 开启远程登录 RENAME USER 'root'@'localhost' TO 'root'@'%'; # 或者直接创建一个新的用户 CREATE USER 'root'@'%' IDENTIFIED BY '12345678'; # 再给予权限操作 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;最后如果要自定义配置 mysqld 话，需要在 /etc 目录中创建一个 my.conf 文件，这个文件就为 mysqld 启动时所需要的配置文件：[mysql] # 设置mysql客户端默认字符集 default-character-set=utf8mb4 [mysqld] skip-name-resolve #设置3306端口 port = 3306 # 设置mysql的安装目录 basedir=/usr/local/mysql # 设置mysql数据库的数据的存放目录 datadir=/usr/local/mysql/data # 允许最大连接数 max_connections=200 # 服务端使用的字符集默认为8比特编码的latin1字符集 character_set_server=utf8mb4 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB lower_case_table_names=1 max_allowed_packet=16M #配置免密登录 skip-grant-tables [mysqld_safe] # 配置日志文件和进程文件 error-log=/usr/local/mysql/logs/error.log pid-file=/usr/local/mysql/logs/mysql.pid最后如果想方便管理，使用系统 systmctl 命令管理 mysqld 程序，此时就的使用 chkconfig 命令添加一个服务管理项目：# 添加 mysqld chkconfig --add /etc/systemd/system/mysql.server # 这样就可以启动一个 mysql 服务 systemctl start mysql.server配置成功之后管理 mysql 就可以使用 systemctl 命令进行管理了。其他资料RHEL9 数据库指南Systemd 入门教程：实战篇
[scode type="lblue"]传统的 Linux 安全机制靠着默认的用户权限、所属组、其他组、 ACL 等功能来实现权限控制，而目前还有另外一套安全机制 SELinux 机制来管理系统安全性，相比默认的权限控制 SELinux 的机制更为强制，默认的用户权限是靠着用户或者超级管理员设置和管理，而 SELinux 强制访问控制机制是由操作系统内核强制实施的，从而使系统更加安全。如果开启了 SELinux 每当操作的时候，SELinux 都会检测是否进程系统调用和网络套节字合法，如果不符合 SELinux 默认的权限就会被拒绝和终止。[/scode]SELinux 提供了一种安全的策略语言，管理员可以使用它来定义系统安全策略。这种策略语言是相当复杂的，但是可以通过使用预定义的策略模块进行简化。这些策略模块是一组预定义的规则集，可以保护常见的应用程序和服务，如 Apache Web 服务器、MySQL 数据库等。总的来说，SELinux 可以帮助管理员保护系统免受恶意攻击和安全漏洞的影响，从而提高系统的安全性和稳定性。然而，由于 SELinux 是一种高级的安全机制，其配置和管理需要一定的技能和经验。一般情况下很少自己起编写 SELinux 的安全策略文件，只会临时或者配置 SELinux 默认的安全策略级别信息，SELinux 配置文件路径 /etc/selinux/config 中，目前很多发行版本已经默认启动该功能。级别说明enforcing默认启动，强制实行安全策略，如果不符合要求就会被禁止permissive该级别会提示用户操作不符合安全策略，但是不会拒绝操作disabled直接禁止功能，因此也不会占用系统资源而策略文件存放在 /etc/selinux/targeted/policy 中，是针对系统的安全策略进行定义的文件。SELinux 有两大策：域（domain）的概念允许定义哪些进程可以访问某些文件和目录；上下文 (context) 默认会给每个系统资源文件设置一个标签，而 SELinux 安全功能就要根据策略文件来检测进程和上下文的关系，从而来控制哪些进程可以访问什么资源。# 查看进程域 ps -Z # 查看资源上下文 ls -Z # 查看目录 ls -Zd /xxx/dir # 查看 selinux 状态 sestatus[scode type="yellow"]在 SELinux 上下文包括以下字段： user（用户）、role（角色）、type（类型）和 security level（安全级别），例如有一个策略规则允许 Apache作为 httpd_t 运行的 Web 服务器进程，访问通常位于 /var/www/html/ 和其他 Web 服务器目录中上下文的文件和目录 httpd_sys_content_t ，但是策略中没有允许规则适用于通常位于 /mysql 和 /data/msyql/ 中的文件，因此不允许访问，即使 Apache 被破坏，一个恶意的脚本可以访问它，也无法访问 /data/msyql/ 目录 。[/scode]因为有默认安全规则配置文件的存在，创建新文件或者移动文件修改文件会导致进程域和上下文不相符的情况，SELinux 默认就会拒绝此类操作导致不符合预期的效果，例如把 nginx 默认的网站目录设置为其他目录导致无法正常运行。此时就要使用下面命令修改进程上下文，使得 nginx 能正常访问到文件：semanage fcontext -a -t httpd_sys_content_t /wwwroot semanage fcontext -a -t httpd_sys_content_t /wwwroot/* # 如果是网络描述符资源，例如端口上下文 semange fcontext -a -t http_port_t -p tcp 8080 # 生效刚刚到修改 restorecon -Rv /wwwroot # 查找符合 http 端口 semanage port -l | grep http[scode type="yellow"]SELinux 默认提供多个命令可以协助帮助设置权限和查看 SELinux 目前运行状态和参数信息：[/scode]命 令作用说明setenforce临时修改 SELinux 状态getenforce获取当前系统 SELinux 状态getsebool [-a]查询 SElinux 策略内各项规则的布尔值setsebool [-P]设置策略并且永久生效修改之后 nginx 进程就能成功访问到 /wwwroot 目录中的文件了， SELinux 没有阻挡读取文件的请求，SELinux 修改的日志相信会存储在 /var/log/audit/audit.log 中，下面开始介绍如何使用 journalctl 查看系统日志信息。juornalctl 日志调试上面介绍了 SELinux 在 Linux 起到作用，并且运行过程中出现问题也会被记录到日志中，方便后期维护审查系统。这里要介绍一款日志审查工具 journalctl ，该工具可以快速帮助管理员检索到需要的日志信息，目前 Linux 中日志系统的日志消息级别有：报错 、警告 、提示 、其他 进行标注，分别对应着不同日志文件：文件作用/var/log/boot.log系统开机自检日志和引导日志/var/log/lastlog用户登录成功的时间，终端名称和 IP 地址/var/log/btmp登录失败的用户时间和终端和 IP 地址/var/log/messages系统各个服务的运行和报错信息/var/log/secure系统安全相关的信息/var/log/wtmp系统启动和关机相关的信息[scode type="yellow"]日志所以类型分为 3 大类：系统日志 、用户日志 、程序日志；系统日志一般都是有系统自动生成的，而用户日志主要就是记录一些用户登录和登出行为信息，程序日志是程序自己生产的，部分系统也会记录一份。常用的参数列表：[/scode]参数作用-k内核日志-b启动日志-u指定服务-n显示行数-p指定类型-f强制输出-since启示时间-until终止时间下面是一些使用例子，注意点事 -p 参数有很多类型：# 只显示错误、冲突和重要告警信息 journalctl -p err..alert # 显示从某个日期 ( 或时间 ) 开始的消息 journalctl --since="2012-10-30 18:17:16" # 显示从某个时间 ( 例如 20分钟前 ) 的消息 journalctl --since "20 min ago" # 显示最新信息，并且显示多少行 journalctl -f -n 10 # 查看特定进程 journalctl /usr/lib/systemd/systemd # 如果知道进程名称查看指定进程名称 journalctl -u sshd # 通过进程 PID 进行 journalctl _PID=1 # 清理两周之前日志 journalctl --vacuum-time=2weeks # 清理日志使总大小小于 100M journalctl --vacuum-size=100M # 查看 SELinux 日志信息 sudo journalctl -t setroubleshoot -n 50 # 等价于这条命令 sudo dmesg | grep -i selinux[scode type="lblue"]-p 参数对应的是日志输出的类型，目前日志类型列表：[/scode]类型作用emerg系统出现严重故障，比如内核崩溃alert应立即修复的故障，比如数据库损坏crit危险比较高的故障，硬盘损坏导致程序运行失败err危险性一般的故障，比如某个服务启动和运行失败warning警告信息比如某个服务参数或者功能出错notice不严重的一般故障，只是需要抽空处理的情况info通用性消息用于提示一些有用的信息debug调试程序所产生的信息none没有优先级，不进行日志记录其他资料How To Use Journalctl to View and Manipulate Systemd LogsSecurity-Enhanced Linux（SELinux）的基本和高级配置Linux Command DocsLinux 命令必知必会
本篇博客将记录一下 Linux 系统下网络配置，会对 Linux 网络配置和基于 Linux 网络的负载均衡的配置介绍，如何在 Linux 系统下配置网络设置是一个极客程序员必须掌握的，如何配置 sshd 服务程序的知识和免密登录、创建虚拟网卡提高系统可靠性。Network ManagerNetworkManager 软件工具的目标是降低在 Linux 与其他类 Unix 操作系统上的计算机网络使用难度，RedHat 在 2004 年发起了 NetworkManager 项目，其目标是让 Linux 用户能够更容易的处理现代的网络需求，特别是无线网络。# 安装 NetWorkManager 软件包 dnf install NetworkManager -y # 查看网卡信息 ifconfig # 查看网卡状态 mii-tools ens160 # 查看网卡详细状态信息 ethtool ens160 # 给某块网卡启一个别名 ip addr 192.168.31.200/24 dev eth0 label eth0:1 # 命令行图像配置 nmtui比较老的版本可以在 /etc/sysconfig/network-scripts/ 目录中，编写对应的网卡脚本文件，Linux 会根据文件内容来设置对应的网卡信息，一个简单模版：# vim ifcfg-ens160 固定前缀 ifcfg- 后面为网卡名称 TYPE=Ethernet # 网络类型 BOOTPROTO=static # 静态分配 NAME=eno16777736 # 网卡名称 ONBOOT=yes # 是否自动启用该网络接口 IPADDR=192.168.10.10 # 分配的 IP NETMASK=255.255.255.0 # 子网掩码 GATEWAY=192.168.10.1 # 网关地址 DNS1=8.8.8.8 # DNS 地址 # 重启网络模块 systemctl restart network以上为老版本系统所使用的方式，不再推荐使用！ RHEL9 和 CentOS 9 Stream 默认使用了 NetworkManager 来管理网络，因此 systemctl restart network 命令已经不再被使用了，目前可以使用 nmcli 命令来管理网络连接，以下是一些常用的 nmcli 命令：nmcli connection show： # 显示所有的网络连接。 nmcli connection up # [连接名称]：启用某个网络连接。 nmcli connection down # [连接名称]：禁用某个网络连接。 nmcli device status # 显示所有网络设备的状态。 nmcli device wifi list # 列出所有可用的 Wi-Fi 热点。 nmcli connection add help # 列出所有适用于 add 的参数信息如果您需要修改网络连接的配置，可以使用以下命令：nmcli connection modify [连接名称] [选项] [值]：修改连接的配置，例如 IP 地址、网关、DNS 等。注意对于任何更改都要谨慎，以免影响系统的网络连接，最可怕是禁止使用网卡导致远程 SSH 连接断开。以下是一些常用的 nmcli connection modify 命令选项：参数名称参数作用ipv4.method设置 IPv4 的获取方式，可选值为 auto、manual、dhcp 或 shared。ipv4.addresses设置 IPv4 地址，格式为 IP 地址/掩码，多个地址之间用逗号分隔。ipv4.gateway设置 IPv4 网关地址。ipv4.dns设置 IPv4 DNS 服务器地址，多个地址之间用逗号分隔。ipv6.method设置 IPv6 的获取方式，可选值为 auto、manual 或 ignore。ipv6.addresses设置 IPv6 地址，格式为 IP 地址/前缀长度，多个地址之间用逗号分隔。ipv6.gateway设置 IPv6 网关地址。ipv6.dns设置 IPv6 DNS 服务器地址，多个地址之间用逗号分隔。例如要修改名为 eth0 的有线网络连接的 IPv4 地址为 192.168.1.2/24、网关为 192.168.1.1，DNS 服务器为 8.8.8.8 和 8.8.4.4，可以执行以下命令：nmcli connection modify eth0 ipv4.addresses 192.168.1.2/24 ipv4.gateway 192.168.1.1 ipv4.dns "8.8.8.8,8.8.4.4"nmctl 不仅可以配置网络参数，还可以基于现有多网卡设备创建新的网络会话连接，基于一张网卡创建一个新的网络环境，例如在公共场所使用一个网络会话，在家里用另外一个网络会话，配置参数如下：参数说明add添加网络会话type网络类型ifname网卡名称con-name会话名称例如使用 eth0 网卡创建一个名字为 house 网络会话：sudo nmcli connection add type ethernet ifname eth0 con-name house最后使用 nmcli 配置这个网络会话参数信息：nmcli connection modify house ip.addresses 192.168.31.1/24 ip.dns "8.8.8.8"如果需要给网卡进行起别名，假设想为 eth0 的网卡设置别名为 my-eth0 ，则可以按照以下步骤操作：# 列出可用设备 nmcli device # 给你网卡起别名 nmcli connection modfiy eth0 connection.id my-eth0 # 删除指定的网卡 nmcli connection delete my-eth0以上是基本网络设备管理和网络设备配置基础，针对环境是物理网线环境，而某些设备采用的是 Wi-Fi 连接的网络的模式，上面的方式将不太适用了，使用下面命令进行配置扫描和连接配置：# 扫描附近的 Wi-Fi nmcli device wifi rescan # 查看可用的 Wi-Fi 网络 nmcli device wifi list # 连接到 Wi-Fi 网络 nmcli device wifi connect SSID password PASSWORD # 端断开 Wi-Fi 网络 nmcli device disconnect wlan0其他 DNS 和 IP 、网络 IP 获取模式可以通过和普通网桥连接到方式一致。虚拟网卡在生产环境大部分服务器都配置多张网卡，我们可以将多张网卡绑定成为一个张虚拟网卡，从而到达提高网络利用率和网络传输速度，更重要的功能是当其中一张物理网卡不能正常工作时，系统会自动切换到另外一张网卡进行工作，把多张网卡绑定成为一张虚拟网卡共用一个固定的 IP 地址。例如创建名字为 bond0 的虚拟网卡，要求这张虚拟网卡必须由物理网卡 ens160 和 ens161 组合而成的，并且指定数据负载模式为轮询：# 先创建虚拟网卡 nmcli connection add type bond ifname bond0 con-name bond0 bond.options "mode=0" # 往虚拟网卡添加物理网卡 nmcli connection add type ethernet slave-type bond con-name b1 ifname ens160 master bond0 nmcli connection add type ethernet slave-type bond con-name b2 ifname ens161 master bond0上面命令中的 connection 参数可简写为 con ，type 类型为 bond 为绑定模式，mode 模式也可以简写成为数字，其他参数在上面基础中已经说明了，mode 模式数字表示对应表为下：数字说明0balance-rr 默认轮询负载1active-backup 故障自动转移2balance-xor 基于MAC地址的负载均衡3broadcast 数据包同时发送到所有从属接口上4802.3ad 这个模式需要交换机支持802.3ad协议5balance-tlb 发送数据包时进行负载均衡，接收数据包时不进行负载均衡6balance-alb 与 5 类似同时也在接收数据包时进行负载均衡配置完成之后，可以用两台在同一网段的电脑测试一下效果：# 一直 ping 查看回包 ping xxx.xxx.xxx.xxx # 关闭其中一个网卡 nmcli connection down b2如果其中一张网卡关闭了，ping 还能正常响应说明配置正常。SSH 连接Secure Shell 是 SSH 的全称，这是一种安全的网络协议可以帮助远程登录管理 Linux 系统，传统方式采用的 FTP 上传文件不安全，后面出现了 SFTP 和 scp 命令进行传输文件；而早期的 Linux 管理采用的是 Telnet 协议进行连接，密码和账号都是明文存储的，从而出现了 SSH 协议和相关实现了 SSH 协议的软件，一般服务器端 Linux 默认都已经安装了 sshd 程序，如果没有安装可以执行下面命令：# 安装 sshd 软件 sudo yum install openssh-server # 设置开机自启动 systemctl enable sshd最后如果是服务端需要配置一些 sshd 软件运行时的规则，默认的配置文件路径位 /etc/ssh/sshd_config 目录中，sshd 支持加密通信的协议，所以可以配置非对称加密使用的密钥对，配置模版如下：# 端口号，如果不指定默认为 22 # Port 22 # 监听地址，如果不指定默认为所有接口 # ListenAddress 0.0.0.0 # 协议版本，可以指定多个，使用逗号分隔，如 "2,1" Protocol 2 # 是否启用公钥验证 PubkeyAuthentication yes # 是否允许密码验证 PasswordAuthentication no # 是否允许 root 用户登录 PermitRootLogin yes # 是否启用 ChallengeResponseAuthentication，即是否启用 PAM 验证 ChallengeResponseAuthentication yes # 是否启用 GSSAPI 验证 GSSAPIAuthentication no # 是否启用 X11 转发 将远程 GUI 程序显示到本地 # 登录时使用参数 -X ssh -X username@remote_host X11Forwarding on # 是否启用 TCP 端口转发 TCPKeepAlive yes # 登录时最大重试次数 MaxAuthTries 3 # 最大同时连接数 MaxSessions 10 # 公钥路径 AuthorizedKeysFile .ssh/authorized_keys # 客户端连接时 banner 的显示内容 # 超级酷可以自定义 ssh 协议登录时的提示消息 # Banner /etc/issue.net # 启用 Syslog 日志 SyslogFacility AUTH LogLevel INFO对应一般需求开启 root 登录工具将可以满足日常开发需求，如果想酷炫一点可以修改客户端连接时 banner 的显示内容，下面这样的：# vim /etc/issue.net ██ ██ ██ ░██░██ ██ ██ ░██ ░██░██ ░░██ ██ ██████ █████ ░██░██████ ░░███ ░░░██░ ██░░░██ ░██░██░░░██ ░██ ░██ ░███████ ░██░██ ░██ ██ ░██ ░██░░░░ ░██░██████ ██ ░░██ ░░██████ ░░ ░░░░░ ░░ ░░ ░░░░░░ welcome to www.ibyte.me ![scode type="yellow"]部分系统在 sshd 中设置 banner 可能不起作用，需要在 \etc\motd 文件中设置，例如：[/scode] ,%%%%%%%%, ,%%/\%%%%/\%% ,%%%\c "" J/%%% %. %%%%/ o o \%%% `%%. %%%% _ |%%% `%% `%%%%(__Y__)%%' // ;%%%%`\-/%%%' (( / `%%%%%%%' \\ .' | \\ / \ | | \\/ ) | | \ /_ | |__ (___________))))))) 一键3连！！更多技术干货 👉 https://blog.leonding.me这样配置之后会在下次 SSH 登录系统时提示保存的内容。如果开启 X11 转发功能，默认可以将远程的 GUI 程序在本地计算机上显示会占用一些网络带宽，但是本地计算机需要安装一些支持 X11 的软件，Windows 上有 WinSCP 和 Xming，MacOS 有 XQuartz，不推荐使用这些还是推荐基于 VNC 协议的软件，Geek 应该直接敲命令才对。基于 SSH 免密钥登录需要生产密钥对，最后将其公钥拷贝远程需要登录到服务器中实现免密登录，执行下面命令# 生成密钥 ssh-keygen -t rsa -b 4096 -C "ding@ibyte.me" # 拷贝密钥 ssh-copy-id xxx.xxx.xxx.xxx光通过 ssh 协议来管理远程服务器功能还是比较单一不能传送文件，目前可以基于 SFTP 一些客户端来实现文件传输，命令行版本可以使用 scp 命令进行传输文件，例如下：# 将某个目录传输到服务器 root 目录下 scp -r /xxx 192.168.31.10:/root # 下载某个目录文件 scp -r 192.168.31.10:/root/jdk /download默认情况下不借助其他第三方 ssh 连接管理软件，可以使用 ssh 默认目录下编写配置文件来管理多个 ssh 连接，配置文件如下：# 编辑 vim ~/.ssh/config Host cs9 HostName 172.16.19.128 User root #Port 22 #IdentityFile ~/.ssh/id_rsa_test默认这种 ssh 会话连接方式在实际操作过程中可能会出现断开情况，不能正常回复会话，在执行某个命令时断开连接就不能继续执行任务，或者执行任务整理一个窗口不能做其他事情，解决这个问题可以使用 terminal multiplever 软件来管理多个 ssh 会话连接，基本的常用命令：# 安装 terminal multiplexer dnf insatll -y tmux # 创建一个会话 tmux new -s backup # 临时离开隐藏会话 tmux detach # 查看当前 tmux 中会话 tmux ls # 恢复之前保存会话 tmux attach -t backup # 直接运行 shell 命令 tmux new "echo 'test text' > test.txt" # 连接到被分享服务器会话中 tmux attach-session -t share上面为基础会话管理命令，下面介绍如何在一个大屏幕上使用 tmux 开启多个小窗口执行更多任务，命令如下：# 分裂出一个新查看，左右分割，默认上上下分割 tmux split-widnow -h # 在多个窗口之间切换，向右切换 —R 向左切换 -L tmux select-pane -R # 上下切换窗口，向上切换 -U 向下切换 -D tmux selelct-pane -U这样就可以不用鼠标也能控制整个 tmux 窗口和管理 ssh 会话；但是在 MacOS 上有快捷键冲突问题，导致不能被正常使用这时得需要重新设置 tmux 的快捷键绑定方式，tmux 快捷键配置文件路径， ~/.tmux.conf ，使用默认 vim 命令编辑添加下面参数：# 将`select-pane`快捷键更改为指定的键，避免与Mac上的快捷键冲突 unbind-key Up unbind-key Down unbind-key Left unbind-key Right bind-key -n C-h select-pane -L bind-key -n C-j select-pane -D bind-key -n C-k select-pane -U bind-key -n C-l select-pane -R bind-key C-n windwo-split -h最后保存并关闭配置文件，然后重新加载配置：tmux source-file ~/.tmux.conf现在你可以使用新的快捷键来切换 tmux 窗口了。其他资料Open SUSE 基本联网知识What Is SSH
最近打算把博客基础环境迁移到 typecho ，其实在 2017 年为的博客就是使用 typecho 搭建的，但是 2019 年考虑到动态网站并且还是基于 php 的博客系统的安全性问题，我就迁移到了静态博客系统 hexo 上，最后 2020 年又迁移到 hugo 上，虽然静态博客系统可以抵抗 xss 反射和 xss 持久化攻击，但是部分功能目前满足不了我，决定迁移会到 typecho 系统上，本篇博客记录一下如何配置 php 运行环境。php-fpm 环境作为一个基本的动态的 web 服务来说必须有要数据库和 http 服务器支持，所以这里就诞生了一些基于开源软件的解决方案，例如 LNMP 的架构，php 本身只是一个动态类型的脚本语言不能作为 http 服务器，这里通过将 php-fpm 和 nginx 整合到一起来提供动态网站的服务。nginx 和 postgresql 服务器采用通过 rpm 软件包的方式安装，相比于通过源代码编译安装的方式要简单多一点，也不需要等待很长的编译时间，而 php-fpm 为了稳定性采用源代码的方式进行编译安装，首先运行下面命令：# 安装 nginx yum install -y nginx相对于 nginx 而 postgresql 数据库安装要复杂，postgresql 目前有很多版本，安装要去官方网站查询对应的版本，并且还需要设置最新的 yum 仓库信息：# 安装 postgresql # Install the repository RPM: sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Disable the built-in PostgreSQL module: sudo dnf -qy module disable postgresql # Install PostgreSQL: sudo dnf install -y postgresql15-server # Optionally initialize the database and enable automatic start: sudo /usr/pgsql-15/bin/postgresql-15-setup initdb sudo systemctl enable postgresql-15 sudo systemctl start postgresql-15nginx 和 postgresql 配置完成之后，接下来将需要进行编译安装 php-fpm 环境，先要解决相关依赖包问题，如何下载 php8 最新版本源码包：# 安装依赖的软件包 sudo yum groupinstall "Development Tools" # 下载 php8 的源码包 wget https://www.php.net/distributions/php-8.2.3.tar.gz # 解压源代码和进入目录 tar zxf php-8.2.3.tar.gz && cd php-8.2.3接下来要对 php-fpm 进行源代码编译安装，首先我们要对进行参数设置支持数据库模块，这里要支持 psql 模块，如果使用的是 mysql 使用 --with-mysql 参数，需要指定 psql 的安装目录 :./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-pdo-pgsql=/usr/pgsql-15 --with-pgsql=/usr/pgsql-15 --with-freetype --with-jpeg --with-zlib --enable-bcmath --enable-shmop --enable-sysvsem --with-curl --enable-mbstring --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-ldap-sasl --with-xmlrpc --enable-soap --with-gettext --enable-fpm --with-zip --enable-gd --with-webp在此过程中会不断检测依赖软件关系和版本，如果遇到版本较低软件可以手动安装和更新，不知道包在 yum 仓库的完整包名可以使用 search 查看：如果默认 yum 不能正常搜索到软件包，可以尝试手动下载对应的软件包安装，第三方软件包查看地址 pkgs.org ，当 configure 全部跑完之后会提示你欢迎使用 php 说明整个环境配置没有问题了，如下图：接下来执行 make 命令，make 命令会根据改成的 configure 文件规则来生产二进制文件，最后使用 make install 命令进行安装：# 生产二进制文件 make # 安装到配置的目录中 make install最后要配置 php 一些环境文件，例如 php.ini 文件可以配置运行环境时参数开启和不开启什么样的功能，在安装目录中是没有这个文件的，需要从源代码包中的模版文件中拷贝；php-fpm 全名是 PHP FastCGI 进程管理器，php-fpm.conf 是用来配置进程的文件，php-fpm 启动后会先读 php.ini ，然后再读相应的 conf 配置文件，conf 配置可以覆盖 php.ini 的配置；另外最后为控制 web 网站的文件，可以配置网站连接性能相关参数：# 配置 ini cp php.ini-development /usr/local/php/lib/php.ini # 创建php-fpm进程管理文件 mv /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf # 配置网站文件 mv /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf最后一步需要把 php-fpm 的服务加入到启动项中，当重新启动服务器会自动加载生效：# 加入启动项，某些系统是 init.d 文件夹 cp sapi/fpm/init.d.php-fpm /etc/init/php-fpm由于 web 服务器在公网上提供服务难免遇到恶意用户情况，并且 php 上一种动态脚本语言，这里需要禁止一些危险的的内置函数，需要在 /usr/local/php/lib/php.ini 的 disable_fucntion 字段添加危险函数列表：disable_functions = passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv最后在还需要将 php-fpm 服务整合到 nginx 中，这样才能使得基于 php 环境网站能正常服务，在 nginx 配置文件中加入下列信息：server { listen 443 ssl http2; #listen [::]:443 ssl http2; server_name www.ibyte.me; root /var/wwwroot/www.ibyte.me; ssl_certificate "/var/wwwroot/ssl/www.ibyte.me.cer"; ssl_certificate_key "/var/wwwroot/ssl/www.ibyte.me.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers PROFILE=SYSTEM; ssl_prefer_server_ciphers on; # security headers add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always; add_header Permissions-Policy "interest-cohort=()" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # logging 设置访问日志 access_log /var/wwwroot/log/nginx_access.log combined buffer=512k flush=1m; error_log /var/wwwroot/log/nginx_error.log warn; # gzip 开启压缩功能 gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; location / { # 设置默认索引 index index.php index.html index.htm; } # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # 这里配置 php-fpm 让 *.php 的请求都去 fastcgi_param 上处理 location ~ .*\.php(\/.*)*$ { # 这里必须和SCRIPT_FILENAME一直，巨坑！ root /var/wwwroot/www.ibyte.me; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_buffers 8 16k; fastcgi_buffer_size 32k; } } server { listen 80; server_name www.ibyte.me; return 301 https://$server_name$request_uri; }推荐默认不要早默认的 nginx.conf 中修改，应该在 conf.d 目录中单独新建一个域名 conf 文件，配置完成之后使用 systemctl restart nginx 启动，如果启动过程中出现了权限问题添加对应权限即可，我使用机器默认开启了 SELinux 安全功能，这时可能导致读取不到 SSL 文件，关闭 SELinux 功能 setenforce 0。SELinux 功能的配置文件路径 /etc/selinux/config 如果要永久关闭此功能，修改配置为禁止使用，但是这样对于生产系统的安全性就会降低，推荐还是在启动 nginx 和 php-fpm 临时关闭，或者将 php-fpm 加入到白名单中，输入下面命令：# 查看 SElinux 状态 sestatus # 加入到白名单中 setsebool -P httpd_read_user_content 1 setsebool -P httpd_can_network_connect 1到此基础软件运行环境配置完成，为了测试环境是否正常运行可以新建一个 phpinfo.php 测试，内容如下：<?php echo phpinfo(); ?>如果能正常显示环境信息说明正常，接下来要去初始化 postgresql 数据库：# 初始化数据库 postgresql-15-setup initdb # 修改默认密码 sudo -u postgres psql # 输入 \password 修改密码最好开启远程登录功能，相关的数据库配置文件在 /var/lib/pgsql/15/data/ 路径下：# 允许远程登录 vim postgresql.conf listen_addresses = '*' # 最后面修改 pg_hba.conf 添加一行 host all all all md5 # 创建新的数据库 create user blog with password 'xxxxx'; create database blog_db owner blog; GRANT ALL PRIVILEGES ON DATABASE blog_db to blog;最后设置防火墙开放一些端口允许远程访问，推荐使用基于内核的 netfilter 的 iptables 工具：# 添加 ssh http https psql iptables -I INPUT -p tcp --dport 22 -j ACCEPT iptables -I INPUT -p tcp --dport 80 -j ACCEPT iptables -I INPUT -p tcp --dport 443 -j ACCEPT iptables -I INPUT -p tcp --dport 5432 -j ACCEPT iptables -P INPUT DROP # 持久化化规则 iptables-save >/etc/iptables-script # 设置开启自动恢复规则 echo '/sbin/iptables-restore /etc/iptables-script' >>/etc/rc.d/rc.local另外一种方式为 firewalld 工具，老版本的系统不会安装需要自己手动安装：yum install -y firewalld firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --permanent --zone=public --add-port=22/tcp firewall-cmd --permanent --zone=public --add-port=5432/tcp firewall-cmd --reload systmctl enable firewalld由于使用的是 typecho 系统生成的数据配置文件在网站根目录中，安全起见设置成为不允许所有人通过 http 访问： # 禁止访问 config.inc.php 文件 location =/config.inc.php { return 403; }至此配置完成，重启 nignx 和 php-fpm 。其他资料PHP disable_functions Executable HandlingChapter 5. Working with SELinuxPostgreSQL新手入门
传统保存备份数据的方式是把某个数据文件拷贝到多个磁盘上保存多个副本，或者采用云盘的方式存储，而在 Linux 中通过一个类似于的功能，可以将多块物理磁盘组成一个逻辑的磁盘单位使用，RAID 代表独立磁盘冗余阵列或廉价磁盘冗余阵列，RAID 的目的是将文件的数据分布在多个磁盘上，这样单个磁盘故障就不会丢失该数据。RAID 技术在计算机中最重要的就是数据，而数据在持久化之后就会被存储到硬盘上，此时对硬件质量和安全性就提出更高要求，例如如果硬盘出现故障之后数据就无法被正常读取导致丢失，RAID 技术就简单来说就是把多块硬盘组成一个整体，从而提升数据可靠性和读写效率问题，缺点明显就是浪费多空间。目前实现 RAID 技术有两种方案，一种软件的方式来实现，另外一种通过硬件的方式，很多在线商城可以购买到支持 RAID 硬盘阵列盒子 有物理设计支持；共同的特点是就是需要多块磁盘，最低需要为 2 块磁盘，不同的 RAID 方案有不同磁盘个数要求。RIAD 0 级别最少需要 2 块硬盘，安全性低，数据依次写入到所有的硬盘中，不具备备份数据和容错能力，其中一块出现异常整个数据就会受到影响，数据可以并行读取和写入不同磁盘中。RIAD 1 级别最少需要 2 块硬盘，安全性高，数据同步写入到所有的硬盘中，数据被冗余存储在多个硬盘中，有一块硬盘可用数据就不会受到影响，以 2 块硬盘为例空间利用率只有 50% ，但是读取数据性能会提升。RIAD 5 级别最少需要 3 块硬盘，和前面方式不同的是采用一种经过数学上一种奇偶数校验来到达数据的完备性，会把真实数据依次存储在不同硬盘中，并且将数据的奇偶数信息存储在不同的硬盘中，当某块硬盘出现问题时，可以通过奇偶数校验反向还原出来原始的数据信息，此种方案能有效提高硬盘利用率和读写效率，也是数据安全性高的。RIAD 1-0 级别是组合了 RIAD 0 和 RIAD 1 两个级别的产物，先对数据采用 RIAD 1 的方式来存储，最后再将其使用 RIAD 0 的方式组合，形成了高可读写和高数据完备性的方案，只要不是损坏的同一阵列的硬盘，那么最高可允许 50% 硬盘损坏而还能正常提供服务。RIAD 的级别远不止这么多，但是最为常用也就这几个，目前使用最多还是 RIAD 5 和 RIAD 10 级别能提供读写性能也能提高数据的安全性，至于怎么创建磁盘阵列可以查看 mdadm 命令。其他资料Virtual Data OptimizerStandard RAID levels软件 RAID 配置 | 储存管理指南 | SUSE
我个人接触 Linux 比较早，最开始喜欢研究网络安全相关的话题，所以第一次使用的 Linux 系统是 Kali Linux，虽然现在有很多的 Linux 发行版本，但大多都是还是使用命令行进行操作的。和 MacOS 不同 Linux 大部分软件生态还是没有 Apple MacOS 完整，这里只的是图形界面和第三方公司开发的软件，而且大部分软件都是一些命令行下的软件，作为一名开发者都有必要掌握一些基础的 Linux 操作命令，这些都是类 Unix 系统，大部分基础命令是相通的，所以介绍同样常用的命令。代替鼠标的命令在 Windows 很少有人使用的 DOS 命令行进行操作，但是在 Linux 下最为常见的是使用命令行，当然 Windows 也可以完全使用的 CMD 命令进行操作来控制电脑，但是大部分人还是使用视窗界面和鼠标。如果计算机操作系统没有图形化界面那么估计到现在还没有普及起来呢。最为常见为目录切换和文件查看，文件和目录创建、文件移动和文件复制、文件下载和文件编辑等操作，下面介绍：# 使用 cd 可以切换目录 cd /home/auula # 返回上一层目录 cd .. # 使用相对路径切换 cd ../../xxx # 快速返回到家目录 cd ~创建文件和创建目录：# 创建一个文件 touch Hello.java # 更新一些文件最后访问时间 touch -d Hello.java # 创建一个目录 mkdir /home/xxxx # 递归创建目录 mkdir -p /home/a/b/c文件移动和创新命名都为同一个 mv 命令：# 将目录a到b中 mv /aaaxx/a /bbbxx/b # 重新命名 mv a.tar b.tar文件复制操作：# 将a.txt文件复制到b目录中 cp /xxxx/a.txt /xxx/b # 使用递归复制 cp -r /xxx/aaa/*.txt /xxx/bbb/从远程的网络服务器上下载程序压缩包，可以有很多种方式，最常见上 wget：# wget 下载文件 -b 后台运行 -c 支持断点续传 wget -b -c https://blog.leonding.me/mirror/trojan-install.sh查看当前工作目录的位置，和当前命令下有什么文件：# 当前位置信息 pwd # 查看当前目录下有什么文件，包括隐藏文件 ls -al查看某个文件内容，如果文件内容太多可以分页查看：# 查看某个文件的内容 cat /xxx/Hello.java # 通过分页查看某个文件的内容 more /xxx/Hello.java当在一个多层次顶层目录中，查看当前目录下的所有子目录：# 查看当前目录，打印目录结构树 tree .查看文件属性信息和文件夹属性信息：# 查看文件属性信息 file Hello.java # stat 查看文件夹和文件信息 stat directory对文本文件内容高级操作：# 查看文件的行数和字节树 wc -l /etc/passwd # 替换文件内容的字符串 cat Hello.java | tr S X对文件内容进行匹配过滤，和比较两个文件内容差异：# 查看符合批评 root 字符串的内容 cat /etc/shadow | grep root # 查看两个文件内容的差异 diff -brief a.java b.java在 Linux 系统中所有的东西都被抽象成了文件，所以如果要删除某个文件得注意！删除文件和目录命令如下：# 删除某个目录，如果目录中有其他文件夹和文件会交互式删除 rm /xxxx # 强制删除，通配符 rm -rf /xxxx/*.go在 Linux 中安装软件传统的方式上通过源代码的方式进行编译安装，这种方式要求管理员自己解决各个软件包的依赖关系，还有一种方式采用的不同 Linux 发行版本中提供软件包管理工具来管理软件。目前为两大主流的发行版本，基于 Debian 和 Redhat 版本的，对于量大不同的包管理工具 deb 和 rpm，这里为主要介绍 rpm 的方式：# 安装某个 rpm 软件包 rpm -ivh xxxx.rpm # 查找安装目录 rpm -ql package | grep bin # 查找某个全包名 rpm -qa | grep java # 卸载某个软件包 rpm -y remove package 在命令行模式没有图形化界面来查看时间，如果需要查看当前系统时间，可以使用 date 命令：# 格式化输出当前系统时间 date "+%Y-%m-%d %H:%M:%S %p" # 当前已经过去多少天 date "+%j" # 设置当前系统时间 date -s "20221101 8:30:00" # 设置时区 timedatectl set-timezone Asia/Shanghai在磁盘上查找某个文件，有时候不知道文件存放在哪里，或者只记得文件的某个名称：# 在某个目录下查询一个以Match开头的.java 源文件 find /xxx -name "Match*.java"此种查询方式效率很低，另外一种方式上通过 locate 命令，此命令会先对目录和文件建立索引，在进行查询：# 在运行 locate 命令之前先要建立索引 updatedb # 在使用 locate 命令查找 locate wheris在 Linux 中的软件都是带有命令本体和软件的使用文件的，要查询使用文档可以使用：# whereis 查询 man 的命令的源代码和帮助文件 whereis man如果需要查询某个命令本体安装路径：# 查询某个程序的路径 which java快速判断一个文件夹和文件是否存在，是否具备读写权限：# 判断目录是否存在 [ -d /directory ] && echo "dir exist" # 判断是否文件 [ -e /xxx/file.java ] && echo "file.java exist" # 判断当前用户是否有写权限 [ -w /xxx/file.java ] && echo "file.java writeable" # 判断当前用户是否有读权限 [ -r /xxx/file.java ] && echo "file.java readable" # 判断当前用户是否有执行权限 [ -x /xxx/xxx.sh ] && echo "xxx.sh executable"在括号中 [ expr ] 可以说其他支持 与、或、非 运算的表达式，例如判断一个当前登录的账号是不是超级管理员：[ ! $USER = root ] && echo "user not administrator"除此之外，还有支持大小比较运算：# 等于 [ 10 -eq 10 ] && echo "equals" # 不等于 [ 100 -ne 10 ] && echo "not equals" # 小与 [ 10 -lt 100 ] && echo "less than" # 大于 [ 100 -gt 10 ] && echo "greater than" # 小于或者等于 [ 10 -le 10 ] && echo "less than & equal" # 大于或者等于 [ 100 -ge 10 ] && echo "greater than & equal" 要判断一个字符串是否为空：# 判断字符串是否为空 [ -z $str ] && echo "is empty"管道和重定向符号，将前面的程序的输出传入到下一个程序的输入：# 将前面一个程序的输出输入下一个程序输入 cat /etc/passwd | grep root # 将标准输出重定向到一个文件中 echo "Hello Shell" > text.txt # 将标准错误和标准输出同时追加重定向到一个文件中 ls -l auula &>> tee.txt # 将文本内容重定向到另外程序标准输入中 ssh root@xxxxxx < deploy.sh查看系统进程和系统状态信息可以使用 PS 命令，大部分命令行软件运行在后台，如果想要停止运行某个软件就需要对应软件 PID ，如下：# 显示系统中全部的进程信息 ps aux # 以进程树的形式显示 pstree # 返回 nginx 的任务 ID pidof nginx # 列出系统支持的全部信号列表： kill -l # 通过信号的方式控制一个进程，强制关闭进程 kill -9 1518定时任务说完最基本的代替鼠标操作的命令之后，介绍一下定时任务，Linux 系统主要的应用场景还是服务器端，服务器大部分不间断运行并且提供对外的服务，某些时候例如定期备份数据库，人工定期备份很麻烦这时可以使用定时任务来使用程序自动化完成。at 命令可以用于在指定时间执行命令，at 只能适用于一种单一任务的定时器，会以 atd 守护进程的形式在后台模式运，会从任务队列里取出任务来执行。at命令的特点就是计划任务的一次性特征，一旦设置的计划任务被执行，则该任务就会被从任务列表库中删除，仅用于执行一次的工作：# 指定一个脚本文件，并且设置执行时间 at -f task.sh 07:35 # 查看已经创建的任务 at -l # 移除某个任务 at -d task_id # 设置在2023年5月18日准时执行某脚本文件 at -f task.sh 05/18/2023 # 使用计划任务设置25分钟后执行某指定脚本文件 at -f task.sh now+25 min另外一种为周期定时器，对应着某个时间段和某个时间点重复做某件事情，在物理上时间都直线运动，一直每分每秒前进，那就要设计一套规则来计算时间和时间单位。在圣经中上帝创造万物用了 7 天，所以现在用 7 天为一个星期的周期，每天 24 小时，一小时为 60 分钟，一分钟为 60 秒... 一年看 2 月份是润年还是平年，组成一个周期。周期定时器的时间设定就以此为单位，来重复某个任务，在 Linux 中有一个为 crond 命令提供此功能，crond 命令在设置的时候要提供日期占位符号： # 每1分钟执行一次 task.sh * * * * * task.sh # 每小时的第3和第15分钟执行 3,15 * * * * task.sh # 每隔两天的上午8点到11点的第3和第15分钟执行 3,15 8-11 */2 * * myCommand # 每月1、10、22日的 4 : 45 重启smb 45 4 1,10,22 * * /etc/init.d/smb restart权限管理大家都说 Linux 系统比 Windows 安全，哪这里的安全体现在哪里？Linux 最初的版本是类似于 Unix 系统设计的，而 Unix 是一个多用户的操作系统，在同一时刻有多个用户可以登录到操作系统中完成任务，那么多个用户如何保证各自的文件安全性呢？ 最初的设计者将引入了权限系统概念，权限系统可以帮助 Root 超级管理员用户管理其他用户的权限，在 Linux 系统中所有的东西皆为文件，对这些文件设置权限，就可以让多个用户之间保持某个规则来访问。Linux 权限系统设计为：用户权限、所属组权限、其他权限；这 3 组权限都属于权限位都是一样的，分别为： R 可读 、W 可写 、 X 可执行，对应目录来说如果没有可执行权限，用户不能进入该目录，有读权限也只能文件列表。权限位对应数字R4W2X1这是最基础的权限位，例如一个文件所有权限位为 rwx-r-x-rwx 换成数字表示为 757，下面介绍基于这些的身份权限操作：# 查看当前用户是谁 who # 查看当前用户的信息 id user_id # 向系统中添加一个名为 user1 用户，并且设置默认的家目录和默认用户ID useradd -d /home/directory_u1 user1 -u 8888 # 修改某个用户信息，将 user1 加入 root 扩展组，并且禁止用户登陆 usermod -G root -L user1 # 修改某个用户的密码 echo "New Passwd" | passwd --stdin user1 # 锁定用户，解锁 -u passwd -l user1 # 查询密码状态 passwd -S user1 # 删除一个用户，并且删除用户家目录 userdel -r user1以上为最基础的用户管理命令，默认创建的用户都有一个默认的与用户名同名的组名，一个用户可以是多个组称之为扩展组，如何创建一个组？默认使用 groupadd 命令即可：# 创建一个组 groupadd www # 将 nignx 用户添加到 www 组 usermod -G www nginx # 更改用户组 ID groupmod -g 6666 www # 删除一个用户组 groupdel www # 查看用户组是否被删除 cat /etc/group | grep www # chown 和 chmod 都属于文件权限控制命令，chown 更能快捷修改文件所有者和所属组 chown nignx:www /wwwroot除此之外还有还有超级权限位，默认的用户、所属组、其他组的权限位无法满足一些需求的时候就会使用到超级权限位，例如 SUID 、 SGID 、SBIT 权限位。SUID 权限位可以让一个原本不属于该用户的文件能拥有属于该文件的权限，例如 ls -l /etc/passwd 让普通用户自己可以修改自己的密码。SGID 权限位可以让用户临时获得文件所属组的权限，例如创建新文件会自动将其归属到对应目录到所属组中。SBIT 权限位，很多第三方翻译成了 粘滞 Sticky Bit 位这是不正确的！应该叫保护位，设置了该权限位的目录，下的文件是无法被其他人删除的只能被所有者删除，例如 /tmp 目录。这三组最高权限位的关系是：SUID = 2^2 、SGID = 2^1 、SBIN = 2^0 ，操作命令：# 设置 SUID chmod u+s xxx # 设置 SGID chmod g+s xxx # 设置 SBIN chmod o+t xxx # 全部组应用规则 chmod a+x xxx超级权限位在第一位，例如一个文件的权限是 1755 那么表示 rwxr-xr-t ，而 rwsrws-w- 表示 6772 ；另外如果一个目录没有 x 权限只有 r 权限，则表示只能读取目录里的内容，不能进入到目录中。除此之外还有一些隐藏更深的隐藏权限属性，chattr 命令其功能是用于更改文件隐藏属性，可用于针对目录和文件的隐藏权限做控制。# 进追加的方式，并且修改之后立即同步到磁盘上 chattr +aS logging.log # 递归设置目录，其中的子文件内容而不能新建或删除文件 chattr -R +i /root以上权限管理命令只能针对的是用户、用户组、其他组来做权限控制，不能准确到某个特定用户身上和用户组上，针对此类需求可以使用文件访问控制列表策略，也称之为 ACL ，通过该技术可以更加精准的控制权限的分配。# 只允许某个用户对目录有读写执行权限 setfacl -Rm u:nginx:rwx /www/wwwroot # 允许某个组对目录有读和访问权限 setfacl -Rm g:www:rx /www/wwwroot # 获取某个目录到 ACL 控制权限信息 getfacl -R /root > dir_root_backup.acl # 从控制信息文件恢复 ACL setfacl --restore dir_root_backup.acl # 取消某个用户的独立权限，-d 是全部清理掉 setfacl -x u:nginx /www/wwwroot除以权限控制规则以外，默认普通用户除用户 ID 为 000 是超级管理员用户之外，其他用户在执行 sbin 系统级的命令时需要提权操作，例如 fdiks 命令对硬盘进行分区操作，这是要管理员用户才有权限执行的命令。如果普通用户要执行此类操作必须临时使用 sudo 命令获得超级权限来执行，另外一种方式为通过 visudo 命令进行配置，指定某个用户能获取什么样的命令权限。# 例如规则: 用户名 允许操作的网段=临时拥有的身份用户 允许操作命令路径 auula 192.168.31.0/255=(root) /usr/bin/cat,/usr/sbin/fdisk # 检查配置 /etc/sudoers 文件配置项是否合规 visudo -c # 配置提权时不需要输入密码 auula ALL=(ALL) NOPASSWD: ALL # 配置组规则，允许对应组旗下人员有高级权限 %www ALL=(ALL) ALL文件系统在所有的操作系统中文件系统都属于一层对于底层硬件磁盘设备的抽象，在 Linux 内核中也是这样的，内核为用户提供一层为 Virtual File System 简称为 VFS 接口，用户操作文件时实际上操作的是操作这层接口。在日常使用 Windows 和 MacOS 时感觉不到硬件变动对于文件系统影响，例如插入一个 USB 设备到电脑中，文件系统会默认将其连接上，而在 Linux 上需要人工手动分区和格式化再将其挂载到指定目录中使用。# 挂载指定硬件设备到指定目录 mount /dev/sda3 /backup # 查看某设备到 UUID blkid /dev/sr0 # 也可以通过硬件设备到唯一 ID 进行挂载 mount UUID=c6f831b1-5ede-4e06-9dd3-eecb42cc4934 /backup # umount 可以用于卸载已经挂载的设备 umount /dev/sdb2 # 查看各个硬件设备和分区挂载的关系 lsblk # 查看当前磁盘和分区使用状态 df -h # 查看某个目录占用内存大小 du -sh大部分设备一段挂载是不会变动的，但是某些情况下服务器重启会导致设备挂载也失效，这时候需要配置开机主动挂载，需要编辑 /etc/fstab 文件中配置信息。以上是针对已经分区并且格式化好的文件系统，如果服务器新加了一块磁盘，此时就需要进行对磁盘进行分区然后使用文件系统格式化，最后挂载到指定目录才能被正常使用。分区会用 fdisk 工具进行分区，此工具目前只能适用于 MBR 的分区表，最多支持 2TB 磁盘，相当于普通服务器完全就够用的；分区完成之后会进行文件系统格式化，这里 Linux 默认提供了 mkfs 命令，次命令有很多子命令例如 mkfs.ext2 mkfs.ext3 mkfs.xfs 文件系统格式，当格式化完成之后使用 mount 挂载到指定目录即可。挂载交换区另外一个文件系统为交换区，可以帮助操作系统在物理内存不够情况下创建新的硬盘交换区，以此充当扩展内存使用，但是磁盘和内存读写速度有明显差距，创建 swap 分区的方式有很多，例如 fdisk 命令，也可以提供 fallocate 命令和 mkswap 命令，注意使用 fdisk 时得需要将其 partition type 修改为 82 表示为交换区格式。# 格式化分区 mkswap /dev/sda3 # 挂载交换区 swapon /dev/sda3 # 设置开机自动挂载 echo "/dev/sda3 none swap defaults 0 0" >> /etc/fstab网络防火墙在 Linux 中防火墙功能可以控制网络数据包数据包的交换，也是保障服务器数据安全性方面最重要的功能，往往服务器上允许部分应用不允许被外界所访问到，这是就可以使用防火墙规则来控制了。 Linux 中的防火墙自身的流量进行过滤，防火墙底层实现基于内核的 netfilter 和 nftables 包过滤器的，而对于普通用户使用的 iptables 和 firewalld 命令只是一个便于配置过滤器规则的工具而已。在网络层次 OSI 模型中，iptables 属于数据链路层的服务，会对网络上的流量按照规则：源地址 、 目的地址 、端口、传输协议 、服务类型信息从上至下进行规则匹配，如果匹配到了则会按照预先设置的规则来处理。# 查看已经配置的策略 iptables -L # 清理掉配置策略 iptables -F # 设置把INPUT链设置默认规则为 DROP iptables -P INPUT DROP # 允许 INPUT 链的 ICMP 流量进入，例如允许 PING 命令样有响应 iptables -I INPUT -p icmp -j ACCEPT # 删除刚刚给 INPUT 添加的规则，指定链和第几条 iptables -D INPUT 1 # 允许某个网段访问并且制定协议和端口 iptables -I INPUT -s 192.168.31.0/24 -p tcp --dport 22 -j ACCEPT # 开放 HTTP 的 80 端口 iptables -I INPUT -p tcp --dport 80 -j ACCEPT # 只接受某个网卡接口的流量 iptables -A INPUT -i eth0 -j ACCEPT # 过滤掉转发的流量，IP 到 IP 到数据包 iptables -A FORWARD -s 192.168.1.0/24 -d 10.0.0.1/24 -j DROP # 禁止某个端口范围进行访问 iptables -A INPUT -p udp -dport 1024:65535 -j REJECT # 永久生效添加策略规则 iptables-saveiptabls 命令功能远不止这么多，最常用是基于 filter 功能对 chain 上的数据包进行过滤，另外一款最新的服务器设置工具是 firewall-cmd 命令，提供 GUI 和命令行版本，并且还有自动化命令参数补全功能，可以查看 firewall-cmd命令 。命令行很酷在类 Unix 系统下有很多的有趣第三方软件，例如 sl 可以跑火车，Linux 是一款类似 Unix 操作系统很多东西都是通用的，很多的第三方开发者也实现不少的命令行软件，这里我介绍几个可以能提升开发效率的命令行软件。第一款是 cheat.sh 这是一款基于在线网络的命令行和编程案例查询工具，可以基于 curl 发起一个 http 请求到达 cheat 服务器，cheat 服务器会返回相应的结果，例如查询 kotlin 语言中的 for 循环语句怎么使用？或者查询某个 Linux 命令不知道也什么参数和如何使用？第二款是 wttr.in 也是基于网络服务的命令行工具，同样要使用 curl 发起一个 http 请求查询数据并且展示到终端上，例如查询上海的未来天气情况?第三款 branch.sh 同样也是基于网络服务的命令行工具，可以使用以下命令来查看您的 Linux 系统信息，还可以测试网络带宽及硬盘读写速率，机器性能如何？第四款为命令行下日历程序，在没有 GUI 界面下绘制日历表格，命令为 cal ：其他资料Linux 命令完全手册Linux Cast 视频Introduction to Linux在 Linux 中使用 shell 脚本自动创建/移除并挂载交换文件A Deep Dive into Iptables and Netfilter Architecture
最近由于 Java 整个社区正在朝着 Cloud Native 方向发展，本人已经好几年没有编写 Java 应用程序。最近准备捡起来玩玩，准备在一台 RHEL9 服务器配置一些 Java 的运行环境，这里运行环境可以是 JRE 但是我选择的整个 JDK 工具包的方式安装，另外 jar 依赖管理采用的是 Maven 所以还需要配置一些 Maven 开发环境。JDK 配置这里由于 Oracle 官方的 JDK 是收费的（如果是个人开发者使用是免费的），这里我采用的 Amazon Corretto JDK 发型版本，基于 OpenJDK 的发行版本有很多。下载 Corretto JDK 的话去 Amazon Corretto 官方网站下载，Linux 下使用 wget 命令下载即可：wget https://corretto.aws/downloads/latest/amazon-corretto-17-aarch64-linux-jdk.rpm这里使用的 RedHat Package Manager 软件包的方式，如果人 tar 包需要自己解压到指定位置，RPM 包直接使用命令安装即可：# 安装软件显示信息和进度条 rpm -ivh amazon-corretto-17-aarch64-linux-jdk.rpm # 支持直接从http和ftp协议拉取下载并且安装 rpm -ivh https://corretto.aws/downloads/latest/amazon-corretto-17-aarch64-linux-jdk.rpm使用的 RPM 默认其实可以不用配置 Java 环境变量信息的，但是部分比较老使用 Java 编写的软件可能会需要查找环境中变量信息，推荐还是配置一下即可：# 配置 Java 环境信息 vim /etc/profile在末尾最后一行添加下列信息，如下：# JAVA_HOME Java程序安装目录 export JAVA_HOME="/usr/lib/jvm/java-17-amazon-corretto" # 运行时需要CLASS信息 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 运行时 export JRE_HOME=/usr/lib/jvm/jre # 导出到环境变量中 export PATH=$JAVA_HOME/bin:$PATH添加完成之后使用 source /etc/profile 刷新系统环境配置信息。Maven 配置光有 JVM 运行环境是不行的，如果要从源代码做自动化构建的话还需要 Maven 环境支持，直接去 Apache Maven 官网下载安装包，命令如下：# 通过 wget 下载安装包 wget https://dlcdn.apache.org/maven/maven-3/3.8.7/binaries/apache-maven-3.8.7-bin.zip # 解压安装到指定目录 unzip apache-maven-3.8.7-bin.zip -d /usr/local/解压完成之后鉴于某些地区网络问题，必须配置加速 Maven 地址，在 /usr/local/apache-maven-3.8.7/conf/settings.xml 添加阿里云地址：<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>当配置完成之后，还需要将 Maven 的安装目录配置到环境变量中，方便其他程序查找使用：export MAVEN_HOME="/usr/local/apache-maven-3.8.7" export PATH=$MAVEN_HOME/bin:$PATH配置完成之后要刷新系统变量，使用命令 source /etc/profile ，最后查看是否全局生效，使用下面命令查看：[root@rhel9 ~]# mvn -v Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29) Maven home: /usr/local/apache-maven-3.8.7 Java version: 17.0.6, vendor: Amazon.com Inc., runtime: /usr/lib/jvm/java-17-amazon-corretto Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "5.14.0-162.6.1.el9_1.aarch64", arch: "aarch64", family: "unix"至此我们就配置完成整个 Java 运行环境和 Jar 依赖管理环境。