普通视图

Received today — 2026年6月7日

Dockerfile

2024年11月24日 00:00

Dockerfile概述

Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。

从应用软件的角度来看,Dockerfile,Docker镜像,Docker容器分别代表软件的三个不同阶段

  • Dockerfile是软件的原材料
  • Docker镜像是软件的交付品
  • Docker容器可以认为是软件的运行态

Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及运维和部署,合力充当Docker体系的基石。

Dockerfile定义了进程需要的一切东西,Dockerfile涉及的内容包括执行代码或者文件,环境变量,依赖包,运行时环境,动态链接库,操作系统发行版,服务进程和内核进程(当应用程序需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等。

Docker镜像,在用Dockerfile定义了一个构建文件之后,docker build时会产生一个Docker镜像,当运行Docker镜像时,会开始真正的提供服务。

Docker容器,是直接提供服务的进程。

Dockerfile构建过程:

  1. docker从基础镜像运行一个容器
  2. 执行一条指令,并对容器进行修改
  3. 执行类似docker commit的操作,提交一个新的镜像层
  4. docker再基于刚刚提交的镜像运行一个新容器
  5. 继续执行Dockerfile的下一条指令,直到所有指令都执行完成

Dockerfile的保留字指令

Dockerfile每个保留字指令均为大写,且后面至少跟随一个参数,指令从上到下顺序执行,用#表示注释。

  • FROM

    基本出现在第一行,意思是要构建的新镜像继承于或者说基于哪个已存在的镜像。

    FROM ubuntu:20.04
  • MAINTAINR

    维护者信息(姓名和邮箱地址)

  • ENV

    用于在构建镜像的过程中设置环境变量,这个环境变量可以在后续的任何RUN指令中使用,就像在命令前面指定了环境变量一样,也可以在其他指令中直接使用这些环境变量,比如WORKDIR $JAVA_HOME

    ENV PATH="/usr/local/bin:${PATH}"
  • RUN

    容器构建(docker build)时需要运行的命令,分为shellexec两种格式

    shell

    RUN apt-get update && apt-get install -y curl

    exec

    RUN ["apt-get", "install", "-y", "curl"]
  • EXPOSE

    声明容器运行时应该开放的端口。它不会自动开启端口,但为外部用户或其他容器提供信息

    EXPOSE 80 443
  • WORKDIR

    指定在创建容器后,终端默认登录进来的工作目录,当执行docker run -it 镜像ID /bash进入容器内部的时候,会默认落脚在哪个目录里

  • USER

    指定镜像以什么用户去执行,如果不指定,默认是root

    USER myuser
  • VOLUME

    容器数据卷,用于数据的保存和持久化,声明容器内的哪个目录需要在运行时挂载数据卷到宿主机上

    VOLUME ["/data"]
  • ADD

    ADD功能相比COPY更加强大,将宿主机内的文件拷贝进镜像,如果源文件是.tar.tar.gz等压缩格式的文件,ADD会自动解压到目标路径。ADD还可以从指定的URL下载文件并复制到容器内。

    ADD <宿主机源路径> <容器内目标路径>
  • COPY

    仅仅执行文件的复制,不支持自动解压或下载

    COPY <宿主机源路径> <容器内目标路径>
  • CMD

    指定容器启动后要做的事情,支持shellexec两种格式,还支持参数列表格式,如果指定了ENTRYPOINT指令,CMD就会被用来指定具体的运行参数

    注意事项

    1.RUNCMD的区别: RUN是构建镜像时执行,CMDdocker run容器启动时执行

    2.Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换

    例如:tomcat的Dockerfile的最后一行是CMD ["catalina.sh", "run"],那么使用docker run -it tomcat /bin/bash命令启动这个镜像时,容器会启动,但是tomcat就不会被启动,因为被run后的命令/bin/bash替换掉了,故容器启动后会运行/bin/bash

  • ENTRYPOINT

    类似于CMD命令,但是不会被docker run后的命令覆盖,而且还会把docker run后的命令当作命令行参数传递给ENTRYPOINT指令指定的程序

    ENTRYPOINT ["可执行文件", "参数1", "参数2", ......]

    ENTRYPOINT可以和CMD一起用,一般是变参才会使用到CMD,这里的CMD等同于是在给ENTRYPOINT传参,当指定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令,它们两个组合后会变成<ENTRYPOINT> "<CMD>"

    案例:

    FROM nginxENTRYPOINT ["nginx", "-c"] #相当于命令加定参CMD ["/etc/nginx/nginx.conf"] #相当于可替换的变参
    按照Dockerfile原样执行传参运行
    Docker命令docker run nginxdocker run nginx /etc/nginx/kms.conf
    容器实际执行nginx -c /etc/nginx/nginx.confnginx -c /etc/nginx/kms.conf

如果写成docker run nginx -c /etc/nginx/kms.conf也可以实现参数替换,-c不会被重复的叠加成docker run nginx -c -c /etc/nginx/kms.conf,因为ENTRYPOINT本身未包含-c,而CMD提供了-c的值

用Dockerfile构建镜像

  编写一个Dockerfile,用于构建一个自带Java17环境的RockyLinux9镜像,构建的镜像基于发行版rockylinux:8.9,新镜像内新增vimnet-tools组件,并将下载好的jdk17拷贝进去,并设置jdk相关的环境变量,让这个镜像构建的容器自带原版镜像不默认安装的一些工具,并且自带jdk17

找到一个空文件夹,vim Dockerfile编辑文件将构建命令写进去

FROM rockylinux:8.9MAINTAINER lzj2023@foxmail.comENV MYPATH /optWORKDIR $MYPATHRUN yum install -y vim net-tools RUN mkdir /opt/jdk# 此处使用相对路径,jdk-17.0.12_linux-x64_bin.tar.gz要和Dockerfile在同一目录ADD jdk-17.0.12_linux-x64_bin.tar.gz /opt/jdkENV JAVA_HOME /opt/jdk/jdk-17.0.12ENV PATH $JAVA_HOME/bin:$PATHCMD ["/bin/bash"]

编写完成后,再将要打包进去的jdk-17.0.12_linux-x64_bin.tar.gz放在同级目录,然后在这个目录内执行docker build命令,会用当前目录(.)下的Dockerfile构建镜像,镜像名称和标签是rockey8_jdk17:1.0.0

docker build -t rockey8_jdk17:1.0.0  .

执行输出

[root@localhost opt]# docker build -t rockey8_jdk17:1.0.0  .Sending build context to Docker daemon  566.8MBStep 1/11 : FROM rockylinux:8.98.9: Pulling from library/rockylinux9088cdb84e39: Pull complete Digest: sha256:9794037624aaa6212aeada1d28861ef5e0a935adaf93e4ef79837119f2a2d04cStatus: Downloaded newer image for rockylinux:8.9 ---> c79048e50f5fStep 2/11 : MAINTAINER lzj2023@foxmail.com ---> Running in 9c40b7e878e1Removing intermediate container 9c40b7e878e1 ---> e58d03a7ebaeStep 3/11 : ENV MYPATH /opt ---> Running in d8448a9b4329Removing intermediate container d8448a9b4329 ---> da5da26abbe3Step 4/11 : WORKDIR $MYPATH ---> Running in ddf20f025c3fRemoving intermediate container ddf20f025c3f ---> 2fe907e79d95Step 5/11 : RUN yum -y install  vim ---> Running in f88378120cc1Rocky Linux 8 - AppStream                       3.4 MB/s |  15 MB     00:04    Rocky Linux 8 - BaseOS                          2.5 MB/s | 9.4 MB     00:03    Rocky Linux 8 - Extras                          5.1 kB/s |  14 kB     00:02    Dependencies resolved.================================================================================ Package            Arch       Version                      Repository     Size================================================================================Installing: vim-enhanced       x86_64     2:8.0.1763-19.el8_6.4        appstream     1.4 MInstalling dependencies: gpm-libs           x86_64     1.20.7-17.el8                appstream      38 k vim-common         x86_64     2:8.0.1763-19.el8_6.4        appstream     6.3 M vim-filesystem     noarch     2:8.0.1763-19.el8_6.4        appstream      49 k which              x86_64     2.21-20.el8                  baseos         49 kTransaction Summary================================================================================Install  5 PackagesTotal download size: 7.8 MInstalled size: 30 MDownloading Packages:[MIRROR] gpm-libs-1.20.7-17.el8.x86_64.rpm: Curl error (52): Server returned nothing (no headers, no data) for http://au.mirrors.cicku.me/rocky/8.10/AppStream/x86_64/os/Packages/g/gpm-libs-1.20.7-17.el8.x86_64.rpm [Empty reply from server][MIRROR] vim-common-8.0.1763-19.el8_6.4.x86_64.rpm: Curl error (52): Server returned nothing (no headers, no data) for http://au.mirrors.cicku.me/rocky/8.10/AppStream/x86_64/os/Packages/v/vim-common-8.0.1763-19.el8_6.4.x86_64.rpm [Empty reply from server][MIRROR] vim-enhanced-8.0.1763-19.el8_6.4.x86_64.rpm: Curl error (52): Server returned nothing (no headers, no data) for http://au.mirrors.cicku.me/rocky/8.10/AppStream/x86_64/os/Packages/v/vim-enhanced-8.0.1763-19.el8_6.4.x86_64.rpm [Empty reply from server](1/5): gpm-libs-1.20.7-17.el8.x86_64.rpm        4.6 kB/s |  38 kB     00:08    (2/5): vim-filesystem-8.0.1763-19.el8_6.4.noarc 172 kB/s |  49 kB     00:00    (3/5): vim-common-8.0.1763-19.el8_6.4.x86_64.rp 725 kB/s | 6.3 MB     00:08    (4/5): vim-enhanced-8.0.1763-19.el8_6.4.x86_64. 144 kB/s | 1.4 MB     00:09    [MIRROR] which-2.21-20.el8.x86_64.rpm: Curl error (52): Server returned nothing (no headers, no data) for http://au.mirrors.cicku.me/rocky/8.10/BaseOS/x86_64/os/Packages/w/which-2.21-20.el8.x86_64.rpm [Empty reply from server](5/5): which-2.21-20.el8.x86_64.rpm             7.4 kB/s |  49 kB     00:06    --------------------------------------------------------------------------------Total                                           449 kB/s | 7.8 MB     00:17     Rocky Linux 8 - AppStream                       1.6 MB/s | 1.6 kB     00:00    Importing GPG key 0x6D745A60: Userid     : "Release Engineering <infrastructure@rockylinux.org>" Fingerprint: 7051 C470 A929 F454 CEBE 37B7 15AF 5DAC 6D74 5A60 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficialKey imported successfullyRunning transaction checkTransaction check succeeded.Running transaction testTransaction test succeeded.Running transaction  Preparing        :                                                        1/1   Installing       : which-2.21-20.el8.x86_64                               1/5   Installing       : vim-filesystem-2:8.0.1763-19.el8_6.4.noarch            2/5   Installing       : vim-common-2:8.0.1763-19.el8_6.4.x86_64                3/5   Installing       : gpm-libs-1.20.7-17.el8.x86_64                          4/5   Running scriptlet: gpm-libs-1.20.7-17.el8.x86_64                          4/5   Installing       : vim-enhanced-2:8.0.1763-19.el8_6.4.x86_64              5/5   Running scriptlet: vim-enhanced-2:8.0.1763-19.el8_6.4.x86_64              5/5   Running scriptlet: vim-common-2:8.0.1763-19.el8_6.4.x86_64                5/5   Verifying        : gpm-libs-1.20.7-17.el8.x86_64                          1/5   Verifying        : vim-common-2:8.0.1763-19.el8_6.4.x86_64                2/5   Verifying        : vim-enhanced-2:8.0.1763-19.el8_6.4.x86_64              3/5   Verifying        : vim-filesystem-2:8.0.1763-19.el8_6.4.noarch            4/5   Verifying        : which-2.21-20.el8.x86_64                               5/5 Installed:  gpm-libs-1.20.7-17.el8.x86_64                                                   vim-common-2:8.0.1763-19.el8_6.4.x86_64                                         vim-enhanced-2:8.0.1763-19.el8_6.4.x86_64                                       vim-filesystem-2:8.0.1763-19.el8_6.4.noarch                                     which-2.21-20.el8.x86_64                                                      Complete!Removing intermediate container f88378120cc1 ---> cb9eb843b319Step 6/11 : RUN yum install -y net-tools ---> Running in 15b13dec8321Last metadata expiration check: 0:00:29 ago on Sun Dec  8 07:29:48 2024.Dependencies resolved.================================================================================ Package         Architecture Version                        Repository    Size================================================================================Installing: net-tools       x86_64       2.0-0.52.20160912git.el8       baseos       321 kTransaction Summary================================================================================Install  1 PackageTotal download size: 321 kInstalled size: 942 kDownloading Packages:net-tools-2.0-0.52.20160912git.el8.x86_64.rpm   262 kB/s | 321 kB     00:01    --------------------------------------------------------------------------------Total                                           101 kB/s | 321 kB     00:03     Running transaction checkTransaction check succeeded.Running transaction testTransaction test succeeded.Running transaction  Preparing        :                                                        1/1   Installing       : net-tools-2.0-0.52.20160912git.el8.x86_64              1/1   Running scriptlet: net-tools-2.0-0.52.20160912git.el8.x86_64              1/1   Verifying        : net-tools-2.0-0.52.20160912git.el8.x86_64              1/1 Installed:  net-tools-2.0-0.52.20160912git.el8.x86_64                                     Complete!Removing intermediate container 15b13dec8321 ---> 2e79057963a9Step 7/11 : RUN mkdir /opt/jdk ---> Running in 69bf76127e8dRemoving intermediate container 69bf76127e8d ---> 25f8d658823fStep 8/11 : ADD jdk-17.0.12_linux-x64_bin.tar.gz /opt/jdk ---> 83d8b92be129Step 9/11 : ENV JAVA_HOME /opt/jdk/jdk-17.0.12 ---> Running in 1f4043adc4cbRemoving intermediate container 1f4043adc4cb ---> 3c05d8189cadStep 10/11 : ENV PATH $JAVA_HOME/bin:$PATH ---> Running in 15d649530173Removing intermediate container 15d649530173 ---> c9f46f990422Step 11/11 : CMD ["/bin/bash"] ---> Running in 02d2ee2073eeRemoving intermediate container 02d2ee2073ee ---> fb5b6a2e5b81Successfully built fb5b6a2e5b81Successfully tagged rockey8_jdk17:1.0.0

提示构建成功,查看一下自己构建的镜像

[root@localhost opt]# docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             SIZErockey8_jdk17       1.0.0               fb5b6a2e5b81        49 seconds ago      642MBrockylinux          8.9                 c79048e50f5f        12 months ago       198MB

用交互模式用刚刚构建成的镜像运行一个容器,执行命令java -version验证打包进去的jdk和环境变量,构建成功!

[root@localhost opt]# docker run -it rockey8_jdk17:1.0.0[root@a4703ffe3841 opt]# java -versionjava version "17.0.12" 2024-07-16 LTSJava(TM) SE Runtime Environment (build 17.0.12+8-LTS-286)Java HotSpot(TM) 64-Bit Server VM (build 17.0.12+8-LTS-286, mixed mode, sharing)

虚悬镜像

定义:REPOSITORYTAG都是<none>的镜像(dangling image),是由于构建和删除镜像时产生一些错误导致的,虚悬镜像会占用空间,因此需要清理掉它们

查出

docker image ls -f dangling=true

清理

docker image prune

IPv4和IPv6

2024年11月23日 00:00

原文地址:https://www.rockylinux.cn/notes/rocky-linux-9-network-configuration.html
原文作者:木子
Rocky Linux 中文社区欢迎您 https://www.rockylinux.cn

IPv4 与 IPv6

在进行 IP 配置之前,我们延伸了解一下 IPv4 与 IPv6 。 IPv4(Internet Protocol version 4)和 IPv6(Internet Protocol version 6)是互联网上用于数据包交换的两个版本的网络层协议。它们是互联网协议套件的核心部分,负责在网络设备之间路由和传递数据。

IPv4

IPv4 是第四版互联网协议,自 1981 年以来一直被广泛使用。IPv4 的特点包括: 地址空间: IPv4 使用 32 位地址,这意味着它可以支持大约 42 亿个独特的 IP 地址。 地址表示: IPv4 地址通常以点分十进制格式表示,例如 192.168.1.1。 地址配置: IPv4 地址可以手动配置(静态)或通过动态主机配置协议(DHCP)自动分配。 分片: IPv4 允许在传输过程中对数据包进行分片,这可以由发送端、接收端或中间路由器处理。 由于互联网的快速增长,IPv4 地址已经耗尽,这促使了对更广泛地址空间协议的需求。 在 IPv4 地址空间中,地址分为公网 IP、私有 IP 和 CGN(Carrier Grade NAT)地址。以下是详细区分:

公网 IP 地址

公网 IP 地址是全球唯一的,可以在整个互联网中进行通信的 IP 地址。它们不属于下列提到的私有 IP 和 CGN 地址的范围。所以,除了以下私有 IP、CGN 地址以及保留地址和特殊用途地址(如多播地址、环回地址等),其他的都属于公网 IP。

私有 IP 地址

私有 IP 地址用于局域网(LAN)内部通信,是不会在互联网中进行路由的。这些地址范围由 IANA(Internet Assigned Numbers Authority)分配:

  1. 10.0.0.0 到 10.255.255.255
  2. 172.16.0.0 到 172.31.255.255
  3. 192.168.0.0 到 192.168.255.255

CGN (Carrier Grade NAT) 地址

CGN 地址也称为共享地址空间,用于 ISP 提供的 NAT 方案,以减少 IPv4 地址的消耗。以下是该范围:

  1. 100.64.0.0 到 100.127.255.255

这些地址也不会在全球互联网中进行路由,用于解决多个用户共享一个公共 IP 地址的需求(Tailscale 用的这个地址段)。

其他特殊地址

还有一些保留和特殊用途的地址,例如:

  • 环回地址: 127.0.0.1
  • 广播地址: 255.255.255.255
  • 多播地址: 224.0.0.0 到 239.255.255.255

IPv6

IPv6 是互联网协议的最新版本,旨在解决 IPv4 地址耗尽的问题,并引入了一些新的特性和改进。IPv6 的特点包括:

  • 地址空间: IPv6 使用 128 位地址,极大地扩展了地址空间,可以支持近乎无限数量的独特 IP 地址。
  • 地址表示: IPv6 地址通常以冒号分隔的十六进制格式表示,例如 2001:0db8:85a3:0000:0000:8a 2 e:0370:7334。
  • 地址配置: IPv6 地址可以通过多种方式配置,包括静态配置、状态无关地址自动配置(SLAAC)和动态主机配置协议版本 6(DHCPv 6)。
  • 无分片: IPv6 设计时取消了路由器的分片功能,要求发送端执行路径最大传输单元(PMTU)发现,并发送适合路径上最小链路 MTU 的数据包。 在 IPv6 中,没有对应 IPv4 的私有 IP 和公网 IP 的概念,但有类似的机制来实现内网和公网的区别与应用。以下是一些重要的 IPv6 地址类型和其用途:

全球单播地址(Global Unicast Address)

全球单播地址就是 IPv6中用于在全球范围内进行通信的唯一地址,类似于 IPv4的公网 IP。其地址范围一般是以 2000::/3 开头。

唯一本地地址(Unique Local Address, ULA)

唯一本地地址在某种程度上类似于 IPv4的私有 IP 地址,用于局域网通信,不会在全球互联网中进行路由。其地址范围是 FC00::/7,也可以细分为以下两个范围:

  • 随机分配的 ULA: FD00::/8,通用情况下会使用这个范围,通过随机生成的方式保证在局部网络内的唯一性。
  • 原始分配的 ULA: FC00::/8,目前未正式广泛使用。

链路本地地址(Link-Local Address)

这些地址只能用于单个网络链路的节点之间,不能路由到其他链路。所有 IPv6 接口在启动时都会自动生成一个链路本地地址以支持邻居发现协议。其地址范围是 FE80::/10

不管是 Linux、macOS 还是 Windows 都会分配一个 Link-Local Address,以inet6 FE80::开头。

# Linux[root@localhost ~]# ifconfigens18: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500        inet 192.168.1.3  netmask 255.255.255.0  broadcast 192.168.1.255        inet6 fe80::486a:e224:31e4:d1fc  prefixlen 64  scopeid 0x20<link>        ether 52:ea:eb:77:3d:fe  txqueuelen 1000  (Ethernet)        RX packets 112410841  bytes 40294807433 (37.5 GiB)        RX errors 0  dropped 0  overruns 0  frame 0        TX packets 64910  bytes 24656852 (23.5 MiB)        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0 # macOS❯ ifconfigen0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500    options=400<CHANNEL_IO>    ether f8:28:19:6a:2b:0f    inet6 fe80::18cd:9189:ab4:ef40%en0 prefixlen 64 secured scopeid 0x6    inet 192.168.1.2 netmask 0xffffff00 broadcast 192.168.1.255    nd6 options=201<PERFORMNUD,DAD>    media: autoselect    status: active # WindowsPS C:\Users\muzi> ipconfigWindows IP 配置以太网适配器 以太网:    连接特定的 DNS 后缀 . . . . . . . :   本地链接 IPv6 地址. . . . . . . . : fe80::530b:7d8a:998a:f3f5%16   IPv4 地址 . . . . . . . . . . . . : 192.168.1.1   子网掩码  . . . . . . . . . . . . : 255.255.255.0   默认网关. . . . . . . . . . . . . : 192.168.1.254

其他类型地址

还有一些其他特殊用途的地址,比如:

  • 多播地址: FF00::/8,用于多播通信。
  • 组播地址: FF00::/8,用于组播通信。

IPv4 与 IPv6 之间的主要区别

  • 地址长度: IPv4 是 32 位,IPv6 是 128 位。
  • 地址表示法: IPv4 使用点分十进制,而 IPv6 使用冒号分隔的十六进制。
  • 地址空间: IPv6 提供了比 IPv4 更广阔的地址空间。
  • NAT 转换: 消除 NAT 以将地址空间从 32 位扩展到 128 位。
  • IPSec 支持: 在 IPv6 中,IPSec 是核心特性的一部分,但同样也需要进行配置,比如采用 strongswan 等。
  • 数据包处理: IPv6 简化了数据包头部,以提高路由效率,并取消了路由器分片功能。
  • 自动配置: IPv6 支持更高级的自动配置能力。
  • 多播和广播: IPv6 支持多播,但不支持 IPv4 那样的网络广播。取而代之,IPv6 使用多播和邻居发现协议来实现网络上的设备发现和配置。

IPv4 与 IPv6 这些区别反映了互联网协议在安全性、效率、可扩展性方面的进步,同时也提出了新的挑战,例如迁移和兼容性问题。随着 IPv6 逐渐被广泛采用,这些挑战将得到解决。

以下两图为 IPv4 与 IPv6 报文头对比:


使用虚拟机安装一个K8s集群

2024年11月17日 00:00

因阿里云加速服务调整,镜像加速服务自2024年7月起不再支持,拉取镜像,下载网络插件等操作,需要科学上网访问DockerHub。

安装全过程均使用ROOT权限。

1.安装前准备工作

这里采用3台CentOS虚拟机进行集群安装,安装前需要环境准备:

  1. 使用虚拟机VMware新建一个NAT类型网络(一般都会默认自带,只进行设置即可),我的起名叫VMnet8,设置VMnet8的子网IP为192.168.228.0,子网掩码为255.255.255.0,网关地址为192.168.228.2,起止IP地址范围192.168.228.3-192.168.228.254,并勾选”将主机虚拟适配器连接到此网络”为物理机分配IP地址,一般会分配192.168.228.1给物理机。

  2. VMware安装3台Linux虚拟机,可以安装一台,然后完整克隆,这里我采用的安装镜像版本是CentOS-7-x86_64-Minimal-2009,安装完成后,设置网络为VMnet8,将IP获取方式由DHCP修改为静态,并将IP地址分别设置为192.168.228.131192.168.228.132192.168.228.133,再分别设置网关,子网掩码,DNS,MAC地址,并保证MAC地址互相不重复。

系统下载地址 https://mirrors.aliyun.com/centos/7/isos/x86_64/

2.安装docker

K8s是个容器编排工具,需要容器环境,这里容器环境采用Docker,每台机器都要安装docker环境

3.安装K8s集群

3.1 安装条件

  • 兼容的Linux发行版(Ubuntu,CentOS等等)
  • 机器需要2GB内存,CPU2核及以上
  • 集群中机器网络彼此互通
  • 集群中不可有重复的主机名
  • 集群中不可有重复的MAC地址

3.2 安装规划

主节点一台机器,从节点两台机器

  • 主节点master

    主机名:k8s131, IP:192.168.228.131

  • 从节点node

    主机名:k8s132, IP:192.168.228.132
    主机名:k8s133, IP:192.168.228.133

3.3 安装前设置

三台机器都要进行设置。

1.设置主机名

在对应IP地址的机器上分别设置主机名为k8s131k8s132k8s133

hostnamectl set-hostname xxx

执行后,检查是否都设置好了

hostname

会话exit退出后重连shell,就会看到shell的计算机名已经变成了自己设置的计算机名root@k8s131

Last login: Thu Nov 21 17:47:02 2024 from 192.168.228.1[root@k8s133 ~]# 

2.关闭交换分区

使用free -m命令,可以查看交换分区情况,安装K8s前,需要关闭交换分区,且永久关闭

[root@localhost ~]# free -m              total        used        free      shared  buff/cache   availableMem:           1819         309        1166           9         342        1360Swap:          2047           0        2047

执行命令,永久关闭交换分区(将Swap设置为 0 0 0)

swapoff -a  sed -ri 's/.*swap.*/#&/' /etc/fstab

3.禁用,并永久禁用SELinux

sudo setenforce 0sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

4.设置允许iptables检查桥接流量

将IPV6流量桥接到IPV4网卡上,是K8s官方要求的做法

cat <<EOF | sudo tee /etc/modules-load.d/k8s.confbr_netfilterEOFcat <<EOF | sudo tee /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1EOFsudo sysctl --system

5.关闭防火墙

CentOS7默认打开防火墙firewalld,3台都需要关闭firewalld

systemctl stop firewalldsystemctl disable firewalld

3.4 安装K8s组件

1.安装kubelet、kubeadm、kubectl

首先添加k8s软件包的yum源到三台机器

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0repo_gpgcheck=0gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg   http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpgexclude=kubelet kubeadm kubectlEOF

三台机器依次执行安装命令

sudo yum install -y kubelet-1.20.9 kubeadm-1.20.9 kubectl-1.20.9 --disableexcludes=kubernetes

三台机器都启动kubelet,并设置开机启动

sudo systemctl enable --now kubelet

不停执行systemctl status kubelet会发现服务一直处于闪断状态,因为kubelet一直在等待

3.5 初始化主节点

使用kubeadm引导集群,初始化主节点。

1.首先所有机器都要先下载需要的镜像

docker pull registry.k8s.io/kube-apiserver:v1.20.9docker pull registry.k8s.io/kube-proxy:v1.20.9docker pull registry.k8s.io/kube-controller-manager:v1.20.9docker pull registry.k8s.io/kube-scheduler:v1.20.9docker pull registry.k8s.io/coredns:1.7.0docker pull registry.k8s.io/etcd:3.4.13-0docker pull registry.k8s.io/pause:3.2

下载完成后docker images验证

2.设置主节点域名映射

3台机器都必须添加master节点(又叫集群的入口节点)的域名映射,IP按实际情况修改。

echo "192.168.228.131  cluster-endpoint" >> /etc/hosts

3.初始化主节点

主节点所在机器上执行命令,初始化主节点

参数解释

  • --service-cidr, --pod-network-cidr 两项设置的网络范围不能重叠,也不能同服务器所在网络范围重叠。
  • --apiserver-advertise-address改成自己主节点的IP。
  • --control-plane-endpoint改为自己设置的主节点域名。

命令

kubeadm init \--image-repository registry.k8s.io \--apiserver-advertise-address=192.168.228.131 \--control-plane-endpoint=cluster-endpoint \--kubernetes-version=v1.20.9 \--service-cidr=10.96.0.0/16 \--pod-network-cidr=192.168.23.0/24 

等待初始化完成后,终端输出提示成功,说明初始化完成。输出的内容需要复制保存备用。

[root@k8s131 ~]# kubeadm init \> --image-repository registry.k8s.io \> --apiserver-advertise-address=192.168.228.131 \> --control-plane-endpoint=cluster-endpoint \> --kubernetes-version=v1.20.9 \> --service-cidr=10.96.0.0/16 \> --pod-network-cidr=192.168.23.0/24 [init] Using Kubernetes version: v1.20.9[preflight] Running pre-flight checks[WARNING Firewalld]: firewalld is active, please ensure ports [6443 10250] are open or your cluster may not function correctly[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/[WARNING SystemVerification]: this Docker version is not on the list of validated versions: 20.10.7. Latest validated version: 19.03[preflight] Pulling images required for setting up a Kubernetes cluster[preflight] This might take a minute or two, depending on the speed of your internet connection[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'[certs] Using certificateDir folder "/etc/kubernetes/pki"[certs] Generating "ca" certificate and key[certs] Generating "apiserver" certificate and key[certs] apiserver serving cert is signed for DNS names [cluster-endpoint k8s131 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.228.131][certs] Generating "apiserver-kubelet-client" certificate and key[certs] Generating "front-proxy-ca" certificate and key[certs] Generating "front-proxy-client" certificate and key[certs] Generating "etcd/ca" certificate and key[certs] Generating "etcd/server" certificate and key[certs] etcd/server serving cert is signed for DNS names [k8s131 localhost] and IPs [192.168.228.131 127.0.0.1 ::1][certs] Generating "etcd/peer" certificate and key[certs] etcd/peer serving cert is signed for DNS names [k8s131 localhost] and IPs [192.168.228.131 127.0.0.1 ::1][certs] Generating "etcd/healthcheck-client" certificate and key[certs] Generating "apiserver-etcd-client" certificate and key[certs] Generating "sa" key and public key[kubeconfig] Using kubeconfig folder "/etc/kubernetes"[kubeconfig] Writing "admin.conf" kubeconfig file[kubeconfig] Writing "kubelet.conf" kubeconfig file[kubeconfig] Writing "controller-manager.conf" kubeconfig file[kubeconfig] Writing "scheduler.conf" kubeconfig file[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"[kubelet-start] Starting the kubelet[control-plane] Using manifest folder "/etc/kubernetes/manifests"[control-plane] Creating static Pod manifest for "kube-apiserver"[control-plane] Creating static Pod manifest for "kube-controller-manager"[control-plane] Creating static Pod manifest for "kube-scheduler"[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s[apiclient] All control plane components are healthy after 14.007116 seconds[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace[kubelet] Creating a ConfigMap "kubelet-config-1.20" in namespace kube-system with the configuration for the kubelets in the cluster[upload-certs] Skipping phase. Please see --upload-certs[mark-control-plane] Marking the node k8s131 as control-plane by adding the labels "node-role.kubernetes.io/master=''" and "node-role.kubernetes.io/control-plane='' (deprecated)"[mark-control-plane] Marking the node k8s131 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule][bootstrap-token] Using token: 4qn4kj.52saric9a3vqnk1w[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key[addons] Applied essential addon: CoreDNS[addons] Applied essential addon: kube-proxyYour Kubernetes control-plane has initialized successfully!To start using your cluster, you need to run the following as a regular user:  mkdir -p $HOME/.kube  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config  sudo chown $(id -u):$(id -g) $HOME/.kube/configAlternatively, if you are the root user, you can run:  export KUBECONFIG=/etc/kubernetes/admin.confYou should now deploy a pod network to the cluster.Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:  https://kubernetes.io/docs/concepts/cluster-administration/addons/You can now join any number of control-plane nodes by copying certificate authoritiesand service account keys on each node and then running the following as root:  kubeadm join cluster-endpoint:6443 --token 4qn4kj.52saric9a3vqnk1w \    --discovery-token-ca-cert-hash sha256:7f12181600006aeb62fb38bcb82582809a9ad1911e49065f1fd13f9c68c95774 \    --control-plane Then you can join any number of worker nodes by running the following on each as root:kubeadm join cluster-endpoint:6443 --token 4qn4kj.52saric9a3vqnk1w \    --discovery-token-ca-cert-hash sha256:7f12181600006aeb62fb38bcb82582809a9ad1911e49065f1fd13f9c68c95774 

4.配置文件目录

按照上述的输出提示“To start using your cluster, you need to run the following as a regular user” 还需要需要在主节点执行下面的命令

mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config

5.部署网络插件

按照上述的输出提示 You should now deploy a pod network to the cluster. Run “kubectl apply -f [podnetwork].yaml”,接下来还需要部署一个pod网络插件到master节点上。

输入命令kubectl get nodes -A,测试下主节点状态,返回NotReady是因为还没有部署网络插件。

[root@k8s131 ~]# kubectl get nodes -ANAME     STATUS     ROLES                  AGE    VERSIONk8s131   NotReady   control-plane,master   5h5m   v1.20.9

k8s支持多种网络插件,例如calico,安装前要先将它的编排文件下载到本地目录

curl https://docs.projectcalico.org/v3.20/manifests/calico.yaml -O  

配置文件中找到以下内容

# - name: CALICO_IPV4POOL_CIDR#   value: "192.168.0.0/16"

将默认的192.168.0.0/16修改为--pod-network-cidr=指定的地址192.168.23.0/24,并解除注释

- name: CALICO_IPV4POOL_CIDR  value: "192.168.23.0/24"

在主节点执行命令,部署网络插件calico,部署过程需要联网下载镜像

kubectl apply -f calico.yaml
[root@k8s131 opt]# kubectl apply -f calico.yaml configmap/calico-config createdcustomresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org createdcustomresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org createdclusterrole.rbac.authorization.k8s.io/calico-kube-controllers createdclusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers createdclusterrole.rbac.authorization.k8s.io/calico-node createdclusterrolebinding.rbac.authorization.k8s.io/calico-node createddaemonset.apps/calico-node createdserviceaccount/calico-node createddeployment.apps/calico-kube-controllers createdserviceaccount/calico-kube-controllers createdpoddisruptionbudget.policy/calico-kube-controllers created

输入kubectl get pods -A查看网络插件pod部署情况

状态为ContainerCreatingInit说明正在下载部署

[root@k8s131 opt]# kubectl get pods -ANAMESPACE     NAME                                       READY   STATUS              RESTARTS   AGEkube-system   calico-kube-controllers-577f77cb5c-wpxh7   0/1     ContainerCreating   0          21mkube-system   calico-node-7wb4z                          0/1     Init:2/3            0          9m3skube-system   coredns-76c6f6bbc9-4q5f9                   0/1     ContainerCreating   0          5h41mkube-system   coredns-76c6f6bbc9-nkdcl                   0/1     ContainerCreating   0          5h41mkube-system   etcd-k8s131                                1/1     Running             1          5h41mkube-system   kube-apiserver-k8s131                      1/1     Running             1          5h41mkube-system   kube-controller-manager-k8s131             1/1     Running             1          5h41mkube-system   kube-proxy-nt5jf                           1/1     Running             1          5h41mkube-system   kube-scheduler-k8s131                      1/1     Running             1          5h41m

完成后状态均为Running

[root@k8s131 opt]# kubectl get pods -ANAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGEkube-system   calico-kube-controllers-577f77cb5c-wpxh7   1/1     Running   0          24mkube-system   calico-node-7wb4z                          1/1     Running   0          12mkube-system   coredns-76c6f6bbc9-4q5f9                   1/1     Running   0          5h44mkube-system   coredns-76c6f6bbc9-nkdcl                   1/1     Running   0          5h44mkube-system   etcd-k8s131                                1/1     Running   1          5h45mkube-system   kube-apiserver-k8s131                      1/1     Running   1          5h45mkube-system   kube-controller-manager-k8s131             1/1     Running   1          5h45mkube-system   kube-proxy-nt5jf                           1/1     Running   1          5h44mkube-system   kube-scheduler-k8s131                      1/1     Running   1          5h45m

再次输入命令kubectl get nodes -A测试主节点状态,已经变为Ready,准备就绪,网络插件到此部署完成。

[root@k8s131 opt]# kubectl get nodes -ANAME     STATUS   ROLES                  AGE     VERSIONk8s131   Ready    control-plane,master   5h53m   v1.20.9

主节点至此初始化完成。

3.6 从节点加入集群

从节点加入集群的命令,在初始化主节点命令输出的内容中就有,在之前复制的输出内容中找到Then you can join any number of worker nodes by running the following on each as root:,并找到它下面的一行命令在每个从节点执行,该命令24小时内有效

kubeadm join cluster-endpoint:6443 --token 4qn4kj.52saric9a3vqnk1w \    --discovery-token-ca-cert-hash sha256:7f12181600006aeb62fb38bcb82582809a9ad1911e49065f1fd13f9c68c95774 

如果令牌忘记了,或者超过了24小时,在master节点上执行下面的命令,生成新的令牌

kubeadm token create --print-join-command

在两个从节点执行这个命令,执行后,以下提示,说明加入成功

[root@k8s132 ~]# kubeadm join cluster-endpoint:6443 --token bjme49.uhg7ubgjn2m16b76     --discovery-token-ca-cert-hash sha256:7f12181600006aeb62fb38bcb82582809a9ad1911e49065f1fd13f9c68c95774[preflight] Running pre-flight checks[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/[WARNING SystemVerification]: this Docker version is not on the list of validated versions: 20.10.7. Latest validated version: 19.03[WARNING Hostname]: hostname "k8s132" could not be reached[WARNING Hostname]: hostname "k8s132": lookup k8s132 on 192.168.228.2:53: server misbehaving[preflight] Reading configuration from the cluster...[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"[kubelet-start] Starting the kubelet[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...This node has joined the cluster:* Certificate signing request was sent to apiserver and a response was received.* The Kubelet was informed of the new secure connection details.Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

回到主节点执行命令kubectl get nodes -A,查看节点状态,可以看到两个从节点了,但是状态是NotReady,此时执行kubectl get pods -A查看pods状态,发现是因为从节点的网络插件未初始化成功完成导致,此时需要耐心等待网络插件加载完成,以我的经验来说加载网络插件需要下载很久。

管理集群要靠主节点,kubectl命令只能在主节点执行

[root@k8s131 ~]# kubectl get nodes -ANAME     STATUS     ROLES                  AGE     VERSIONk8s131   Ready      control-plane,master   2d23h   v1.20.9k8s132   NotReady   <none>                 6m49s   v1.20.9k8s133   NotReady   <none>                 6m41s   v1.20.9
[root@k8s131 ~]# kubectl get pods -ANAMESPACE     NAME                                       READY   STATUS                  RESTARTS   AGEkube-system   calico-kube-controllers-577f77cb5c-wpxh7   1/1     Running                 1          2d18hkube-system   calico-node-7wb4z                          1/1     Running                 1          2d18hkube-system   calico-node-plcdp                          0/1     Init:ImagePullBackOff   0          8m22skube-system   calico-node-zwmrg                          0/1     Init:ImagePullBackOff   0          8m30skube-system   coredns-76c6f6bbc9-4q5f9                   1/1     Running                 1          2d23hkube-system   coredns-76c6f6bbc9-nkdcl                   1/1     Running                 1          2d23hkube-system   etcd-k8s131                                1/1     Running                 2          2d23hkube-system   kube-apiserver-k8s131                      1/1     Running                 2          2d23hkube-system   kube-controller-manager-k8s131             1/1     Running                 2          2d23hkube-system   kube-proxy-flrq9                           1/1     Running                 0          8m22skube-system   kube-proxy-nt5jf                           1/1     Running                 2          2d23hkube-system   kube-proxy-tcrjv                           1/1     Running                 0          8m30skube-system   kube-scheduler-k8s131                      1/1     Running                 2          2d23h

等到kubectl get pods -A全部变为Running,再次测试kubectl get nodes -A

[root@k8s131 ~]# kubectl get nodes -ANAME     STATUS   ROLES                  AGE   VERSIONk8s131   Ready    control-plane,master   3d    v1.20.9k8s132   Ready    <none>                 23m   v1.20.9k8s133   Ready    <none>                 22m   v1.20.9

所有节点状态都是Ready,至此,两个从节点加入了K8s集群并进入就绪状态,整个K8s集群安装完成!

使用GraalVM原生编译打包SpringBoot工程

2024年11月10日 00:00

1.GraalVM

GraalVM (https://www.graalvm.org/) 是一个高性能的JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还提供JavaScript,python和许多其他流行语言的运行时。

GraalVM提供了两种运行Java应用程序的方式:

  1. 在hotspot jvm上使用graal即时编译器(JIT)
  2. 作为预先编译的本机可执行文件运行

GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部语言的调用成本。

2.Windows平台安装和配置GraalVM

Windows平台环境安装和配置比较复杂

2.1 安装Visual Studio

Visual Studio(特别是其中的 Visual C++ 工具)在 Windows 系统上提供了 C/C++ 编译器和相关工具链,它们是用来编译和链接本地代码的。当使用 GraalVM 在 Windows 上构建本地可执行文件时,需要依赖 Visual Studio 提供的这些编译工具来完成编译和链接的过程。

安装界面,勾选使用C++的桌面开发

等待安装完成后,测试x64 Native Tools Command Prompt for VS 2022

以管理员身份测试打开x64 Native Tools Command Prompt for VS 2022

2.2 Windows安装GraalVM并配置

打开社区版GraalVM下载的GitHub页面
https://github.com/graalvm/graalvm-ce-builds/releases

以22.3.3版Java17为例,找到22.3.3版本,分别下载Windows平台的GraalVM graalvm-ce-java17-windows-amd64-22.3.3.zip和调用底层工具的原生镜像支持工具native-image-installable-svm-java17-windows-amd64-22.3.3.jar

下载地址:

  • graalvm-ce-java17-windows-amd64-22.3.3.zip

https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.3/graalvm-ce-java17-windows-amd64-22.3.3.zip

  • native-image-installable-svm-java17-windows-amd64-22.3.3.jar

https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.3/native-image-installable-svm-java17-windows-amd64-22.3.3.jar

设置环境变量

将GraalVM压缩包解压到F:\graalvm-ce-java17-22.3.3,然后设置环境变量JAVA_HOMEF:\graalvm-ce-java17-22.3.3,设置环境变量PATH新增%JAVA_HOME%/bin

检查环境变量生效

java -version

安装native-image

在jar包目录打开cmd窗口,输入命令,完成安装

gu install --file ./native-image-installable-svm-java17-windows-amd64-22.3.3.jar

输入命令,测试是否安装成功

native-image

至此,Windows平台GraalVM和支持将应用打包成本地镜像的工具安装配置完成。

2.3 新建简单项目测试编译打包

打开IDEA,新建项目springboot3,JDK选择安装的graalvm-ce-java17,然后先编写一段非常简单的Java代码,然后将其编译为原生.exe可执行文件

public class Main {    public static void main(String[] args) {        System.out.printf("hello graalvm!");    }}

用Maven将项目打包为jar包

打开x64 Native Tools Command Prompt for VS 2022工具,使用底层能力运行native-image工具,将class编译为.exe。

以管理员身份打开x64 Native Tools Command Prompt for VS 2022,并CD切换到jar包所在target目录,然后执行命令

-cp 编译
-o 目标.exe文件名称

native-image -cp ./springboot3-1.0-SNAPSHOT.jar org.example.Main -o springboot3

编译完成,生成Windows平台的.exe可执行文件

打开CMD,执行exe文件,输出代码运行结果,和Java虚拟机解释执行结果相同

2.4 新建SpringBoot3项目测试编译打包

首先要在Windows下配置三个环境变量,每个人环境和位置不同,需要自己按照自己的实际情况新增或修改。

1.path

path环境变量新增以下路径

F:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\bin\Hostx64\x64

2.lib

新建lib环境变量,并将以下路径配置进去,之间用英文分号;间隔,以下位置目录均为VS创建

F:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\lib\x64F:\Windows Kits\10\Lib\10.0.22621.0\ucrt\x64F:\Windows Kits\10\Lib\10.0.22621.0\um\x64

3.INCLUDE(大写)

新建INCLUDE环境变量,并将以下路径配置进去,之间用英文分号;间隔,以下位置目录均为VS创建

F:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\includeF:\Windows Kits\10\Include\10.0.22621.0\sharedF:\Windows Kits\10\Include\10.0.22621.0\ucrtF:\Windows Kits\10\Include\10.0.22621.0\umF:\Windows Kits\10\Include\10.0.22621.0\winrt

! ! !环境变量配置好后,一定要重新打开CMD窗口编译打包,使用Intellij IDEA的,一定也要重启IDEA。

接下来,编写一个简单的SpringBoot应用,并编译打包为原生.exe文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>3.0.6</version>    </parent>    <groupId>org.example</groupId>    <artifactId>springboot3</artifactId>    <version>1.0-SNAPSHOT</version>    <properties>        <maven.compiler.source>17</maven.compiler.source>        <maven.compiler.target>17</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.graalvm.buildtools</groupId>                <artifactId>native-maven-plugin</artifactId>            </plugin>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

org.example.controller.TestController

package org.example.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("test")public class TestController {    @GetMapping("hello")    public String hello(String s) {        return "hello " + s;    }}

org.example.Main

package org.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Main {    public static void main(String[] args) {        SpringApplication.run(Main.class);    }}

application.yml

server:  port: 8081

接下来先用普通的编译命令将代码编译为.class文件。

然后,执行spring-bootprocess-aot插件,进行编译前的前置处理。

前置处理完成后,target目录下生成了一些AOT编译相关的文件,用于指定主类的位置等

最后,运行native插件的build启动native-image,调用VS工具链将工程编译为二进制.exe文件

开始编译后等待编译完成


我的IDEA输出:

[INFO] Scanning for projects...[INFO] [INFO] ----------------------< org.example:springboot3 >-----------------------[INFO] Building springboot3 1.0-SNAPSHOT[INFO] --------------------------------[ jar ]---------------------------------[INFO] [INFO] --- native-maven-plugin:0.9.21:build (default-cli) @ springboot3 ---[WARNING] 'native:build' goal is deprecated. Use 'native:compile-no-fork' instead.[INFO] Found GraalVM installation from JAVA_HOME variable.[INFO] Executing: F:\graalvm-ce-java17-22.3.3\bin\native-image.cmd @target\tmp\native-image-3043011469918092793.args========================================================================================================================GraalVM Native Image: Generating 'springboot3' (executable)...========================================================================================================================[1/7] Initializing...                                                                                   (21.0s @ 0.17GB) Version info: 'GraalVM 22.3.3 Java 17 CE' Java version info: '17.0.8+7-jvmci-22.3-b22' C compiler: cl.exe (microsoft, x64, 19.41.34123) Garbage collector: Serial GC 1 user-specific feature(s) - org.springframework.aot.nativex.feature.PreComputeFieldFeatureField org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build timeField org.springframework.core.NativeDetector#imageCode set to true at build timeField org.springframework.core.KotlinDetector#kotlinPresent set to false at build timeField org.springframework.core.KotlinDetector#kotlinReflectPresent set to false at build timeField org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build timeField org.springframework.cglib.core.AbstractClassGenerator#imageCode set to true at build timeField org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory#PRESENT set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#romePresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jaxb2Present set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2Present set to true at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2XmlPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2SmilePresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2CborPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#gsonPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jsonbPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationCborPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationJsonPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.boot.logging.java.JavaLoggingSystem$Factory#PRESENT set to true at build timeField org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory#PRESENT set to true at build timeField org.springframework.http.converter.json.Jackson2ObjectMapperBuilder#jackson2XmlPresent set to false at build timeField org.springframework.web.servlet.view.InternalResourceViewResolver#jstlPresent set to false at build timeField org.springframework.web.context.support.StandardServletEnvironment#jndiPresent set to true at build timeField org.springframework.boot.logging.logback.LogbackLoggingSystemProperties#JBOSS_LOGGING_PRESENT set to false at build timeField org.springframework.web.context.support.WebApplicationContextUtils#jsfPresent set to false at build timeField org.springframework.web.context.request.RequestContextHolder#jsfPresent set to false at build timeField org.springframework.context.event.ApplicationListenerMethodAdapter#reactiveStreamsPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jaxb2Present set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2Present set to true at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2XmlPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2SmilePresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#gsonPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jsonbPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationCborPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationJsonPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#reactorPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#rxjava3Present set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#kotlinCoroutinesPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#mutinyPresent set to false at build timeField org.springframework.web.client.RestTemplate#romePresent set to false at build timeField org.springframework.web.client.RestTemplate#jaxb2Present set to false at build timeField org.springframework.web.client.RestTemplate#jackson2Present set to true at build timeField org.springframework.web.client.RestTemplate#jackson2XmlPresent set to false at build timeField org.springframework.web.client.RestTemplate#jackson2SmilePresent set to false at build timeField org.springframework.web.client.RestTemplate#jackson2CborPresent set to false at build timeField org.springframework.web.client.RestTemplate#gsonPresent set to false at build timeField org.springframework.web.client.RestTemplate#jsonbPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationCborPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationJsonPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.boot.autoconfigure.web.format.WebConversionService#JSR_354_PRESENT set to false at build timeSLF4J: No SLF4J providers were found.SLF4J: Defaulting to no-operation (NOP) logger implementationSLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.Field org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler#isContextPropagationPresent set to false at build timeField org.springframework.web.servlet.support.RequestContext#jstlPresent set to false at build time[2/7] Performing analysis...  [**********]                                                              (98.5s @ 1.27GB)  15,315 (92.75%) of 16,513 classes reachable  24,931 (67.59%) of 36,886 fields reachable  73,546 (62.21%) of 118,224 methods reachable     802 classes,   244 fields, and 4,549 methods registered for reflection      83 classes,    78 fields, and    68 methods registered for JNI access       5 native libraries: crypt32, ncrypt, psapi, version, winhttp[3/7] Building universe...                                                                               (9.2s @ 1.48GB)[4/7] Parsing methods...      [**]                                                                       (3.0s @ 1.71GB)[5/7] Inlining methods...     [***]                                                                      (2.1s @ 1.75GB)[6/7] Compiling methods...    [*********]                                                               (92.6s @ 1.80GB)[7/7] Creating image...                                                                                (162.3s @ 1.29GB)  34.44MB (52.32%) for code area:    48,187 compilation units  30.94MB (47.01%) for image heap:  354,772 objects and 118 resources 445.88KB ( 0.66%) for other data  65.81MB in total------------------------------------------------------------------------------------------------------------------------Top 10 packages in code area:                               Top 10 object types in image heap:   1.64MB sun.security.ssl                                     7.33MB byte[] for code metadata   1.05MB java.util                                            3.64MB java.lang.Class 835.67KB java.lang.invoke                                     3.41MB java.lang.String 725.04KB com.sun.crypto.provider                              2.87MB byte[] for general heap data 560.78KB org.apache.catalina.core                             2.83MB byte[] for java.lang.String 527.20KB org.apache.tomcat.util.net                           1.29MB com.oracle.svm.core.hub.DynamicHubCompanion 495.45KB org.apache.coyote.http2                            880.07KB byte[] for reflection metadata 477.20KB java.lang                                          775.15KB byte[] for embedded resources 470.10KB c.s.org.apache.xerces.internal.impl.xs.traversers  664.68KB java.lang.String[] 467.89KB java.util.concurrent                               621.94KB java.util.HashMap$Node  26.94MB for 621 more packages                                5.69MB for 3067 more object types------------------------------------------------------------------------------------------------------------------------                      134.1s (26.7% of total time) in 116 GCs | Peak RSS: 2.60GB | CPU load: 3.09------------------------------------------------------------------------------------------------------------------------Produced artifacts: C:\Users\LiuZijian\IdeaProjects\springboot3\target\springboot3.build_artifacts.txt (txt) C:\Users\LiuZijian\IdeaProjects\springboot3\target\springboot3.exe (executable)========================================================================================================================Finished generating 'springboot3' in 8m 20s.[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:  08:30 min[INFO] Finished at: 2024-11-14T23:22:01+08:00[INFO] ------------------------------------------------------------------------Process finished with exit code 0

编译完成,得到.exe文件

运行编译成的.exe文件并测试

.exe文件很快启动,并可以正常处理http请求,虽然命令窗口打印存在问题,但是接口是能调通的,文章写到这已经很晚了,故这个问题先留着后续有空解决…

3.linux平台安装和配置GraalVM

Linux平台安装和配置GraalVM,我使用Ubuntu22进行测试

3.1 安装Linux编译工具链

编译native-image,需要安装Linux下的编译环境

sudo apt-get install build-essential libz-dev zlib1g-dev

! tips: 如果是redhat系列linux,可能需要这样安装

sudo yum install -y gcc glibc glibc-devel zlib-devel

3.2 Linux下载安装GraalVM

接下来,和Windows系统一样,下载GraalVMnative-image的Linux x64版本,我在这里还是选择和Windows一样的22.3.3版本。

下载地址仍然是 https://github.com/graalvm/graalvm-ce-builds/releases

  • graalvm-ce-java17-linux-amd64-22.3.3.tar.gz

https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.3/graalvm-ce-java17-linux-amd64-22.3.3.tar.gz

  • native-image-installable-svm-java17-linux-amd64-22.3.3.jar

https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.3/native-image-installable-svm-java17-linux-amd64-22.3.3.jar

将下载好的两个文件移动到/opt目录下,解压graalvm-ce-java17-linux-amd64-22.3.3.tar.gz到路径/opt并设置环境变量

解压到/opt

tar -zxvf ./graalvm-ce-java17-linux-amd64-22.3.3.tar.gz

打开/etc/profile设置环境变量

vim /etc/profile

将下面两句放在文件最末尾

export JAVA_HOME=/opt/graalvm-ce-java17-22.3.3export PATH=$PATH:$JAVA_HOME/bin

刷新,使环境变量生效

source /etc/profile

返回/opt目录,安装native-image.jar原生镜像打包工具包,命令和Windows之前安装的命令完全相同

cd /optgu install --file ./native-image-installable-svm-java17-linux-amd64-22.3.3.jar

验证JAVA_HOME变量是否生效,native-image.jar是否正确安装

java -versionnative-image

3.3 编译打包SpringBoot项目

首先需要安装Linux环境的maven,并设置好环境变量,然后将工程源码拷贝到/opt目录下,然后依次执行maven命令。

先用普通的编译命令将代码编译为.class文件

mvn run compile

然后,执行spring-bootprocess-aot插件,进行编译前的前置处理

mvn spring-boot:process-aot

开始编译,运行native插件的build启动native-image,调用Linux底层工具链将工程编译为Linux平台的二进制文件

mvn  native:build

终端输出

root@lzj-virtual-machine:/opt/springboot3# mvn  native:build[INFO] Scanning for projects...[INFO] [INFO] ----------------------< org.example:springboot3 >-----------------------[INFO] Building springboot3 1.0-SNAPSHOT[INFO]   from pom.xml[INFO] --------------------------------[ jar ]---------------------------------[INFO] [INFO] --- native:0.9.21:build (default-cli) @ springboot3 ---[WARNING] 'native:build' goal is deprecated. Use 'native:compile-no-fork' instead.[INFO] Found GraalVM installation from JAVA_HOME variable.[INFO] Executing: /opt/graalvm-ce-java17-22.3.3/bin/native-image -cp /opt/springboot3/target/classes:/root/.m2/repository/org/springframework/boot/spring-boot-starter/3.0.6/spring-boot-starter-3.0.6.jar:/root/.m2/repository/ch/qos/logback/logback-classic/1.4.7/logback-classic-1.4.7.jar:/root/.m2/repository/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar:/root/.m2/repository/io/micrometer/micrometer-observation/1.10.6/micrometer-observation-1.10.6.jar:/root/.m2/repository/org/springframework/spring-expression/6.0.8/spring-expression-6.0.8.jar:/root/.m2/repository/ch/qos/logback/logback-core/1.4.7/logback-core-1.4.7.jar:/root/.m2/repository/org/springframework/spring-webmvc/6.0.8/spring-webmvc-6.0.8.jar:/root/.m2/repository/org/springframework/boot/spring-boot/3.0.6/spring-boot-3.0.6.jar:/root/.m2/repository/org/springframework/boot/spring-boot-starter-logging/3.0.6/spring-boot-starter-logging-3.0.6.jar:/root/.m2/repository/org/springframework/boot/spring-boot-starter-json/3.0.6/spring-boot-starter-json-3.0.6.jar:/root/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.14.2/jackson-datatype-jsr310-2.14.2.jar:/root/.m2/repository/org/springframework/spring-aop/6.0.8/spring-aop-6.0.8.jar:/root/.m2/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/root/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.8/tomcat-embed-websocket-10.1.8.jar:/root/.m2/repository/org/springframework/spring-context/6.0.8/spring-context-6.0.8.jar:/root/.m2/repository/org/slf4j/jul-to-slf4j/2.0.7/jul-to-slf4j-2.0.7.jar:/root/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2.jar:/root/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.14.2/jackson-datatype-jdk8-2.14.2.jar:/root/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/3.0.6/spring-boot-autoconfigure-3.0.6.jar:/root/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.8/tomcat-embed-el-10.1.8.jar:/root/.m2/repository/io/micrometer/micrometer-commons/1.10.6/micrometer-commons-1.10.6.jar:/root/.m2/repository/org/apache/logging/log4j/log4j-api/2.19.0/log4j-api-2.19.0.jar:/root/.m2/repository/org/springframework/spring-web/6.0.8/spring-web-6.0.8.jar:/root/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.14.2/jackson-module-parameter-names-2.14.2.jar:/root/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.19.0/log4j-to-slf4j-2.19.0.jar:/root/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.8/tomcat-embed-core-10.1.8.jar:/root/.m2/repository/org/springframework/spring-core/6.0.8/spring-core-6.0.8.jar:/root/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2.jar:/root/.m2/repository/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar:/root/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2.jar:/root/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.0.6/spring-boot-starter-web-3.0.6.jar:/root/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/3.0.6/spring-boot-starter-tomcat-3.0.6.jar:/root/.m2/repository/org/springframework/spring-jcl/6.0.8/spring-jcl-6.0.8.jar:/root/.m2/repository/org/springframework/spring-beans/6.0.8/spring-beans-6.0.8.jar --no-fallback -H:Path=/opt/springboot3/target -H:Name=springboot3========================================================================================================================GraalVM Native Image: Generating 'springboot3' (executable)...========================================================================================================================[1/7] Initializing...                                                                                   (38.9s @ 0.18GB) Version info: 'GraalVM 22.3.3 Java 17 CE' Java version info: '17.0.8+7-jvmci-22.3-b22' C compiler: gcc (linux, x86_64, 11.4.0) Garbage collector: Serial GC 1 user-specific feature(s) - org.springframework.aot.nativex.feature.PreComputeFieldFeatureField org.springframework.core.NativeDetector#imageCode set to true at build timeField org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build timeField org.springframework.core.KotlinDetector#kotlinPresent set to false at build timeField org.springframework.core.KotlinDetector#kotlinReflectPresent set to false at build timeField org.springframework.cglib.core.AbstractClassGenerator#imageCode set to true at build timeField org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build timeField org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory#PRESENT set to true at build timeField org.springframework.web.servlet.view.InternalResourceViewResolver#jstlPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#romePresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jaxb2Present set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2Present set to true at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2XmlPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2SmilePresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jackson2CborPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#gsonPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#jsonbPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationCborPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationJsonPresent set to false at build timeField org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory#PRESENT set to false at build timeField org.springframework.boot.logging.java.JavaLoggingSystem$Factory#PRESENT set to true at build timeField org.springframework.http.converter.json.Jackson2ObjectMapperBuilder#jackson2XmlPresent set to false at build timeField org.springframework.web.context.support.StandardServletEnvironment#jndiPresent set to true at build timeField org.springframework.web.context.support.WebApplicationContextUtils#jsfPresent set to false at build timeField org.springframework.web.context.request.RequestContextHolder#jsfPresent set to false at build timeField org.springframework.boot.logging.logback.LogbackLoggingSystemProperties#JBOSS_LOGGING_PRESENT set to false at build timeField org.springframework.context.event.ApplicationListenerMethodAdapter#reactiveStreamsPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jaxb2Present set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2Present set to true at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2XmlPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jackson2SmilePresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#gsonPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#jsonbPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationCborPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationJsonPresent set to false at build timeField org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.web.client.RestTemplate#romePresent set to false at build timeField org.springframework.web.client.RestTemplate#jaxb2Present set to false at build timeField org.springframework.web.client.RestTemplate#jackson2Present set to true at build timeField org.springframework.web.client.RestTemplate#jackson2XmlPresent set to false at build timeField org.springframework.web.client.RestTemplate#jackson2SmilePresent set to false at build timeField org.springframework.web.client.RestTemplate#jackson2CborPresent set to false at build timeField org.springframework.web.client.RestTemplate#gsonPresent set to false at build timeField org.springframework.web.client.RestTemplate#jsonbPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationCborPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationJsonPresent set to false at build timeField org.springframework.web.client.RestTemplate#kotlinSerializationProtobufPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#reactorPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#rxjava3Present set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#kotlinCoroutinesPresent set to false at build timeField org.springframework.core.ReactiveAdapterRegistry#mutinyPresent set to false at build timeField org.springframework.boot.autoconfigure.web.format.WebConversionService#JSR_354_PRESENT set to false at build timeSLF4J: No SLF4J providers were found.SLF4J: Defaulting to no-operation (NOP) logger implementationSLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.Field org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler#isContextPropagationPresent set to false at build timeField org.springframework.web.servlet.support.RequestContext#jstlPresent set to false at build time[2/7] Performing analysis...  [*********]                                                              (450.4s @ 2.25GB)  15,331 (92.43%) of 16,586 classes reachable  24,926 (67.58%) of 36,883 fields reachable  73,471 (62.05%) of 118,406 methods reachable     784 classes,   246 fields, and 4,536 methods registered for reflection      64 classes,    70 fields, and    55 methods registered for JNI access       4 native libraries: dl, pthread, rt, z[3/7] Building universe...                                                                              (71.4s @ 1.81GB)[4/7] Parsing methods...      [*******]                                                                 (48.4s @ 1.67GB)[5/7] Inlining methods...     [***]                                                                     (18.7s @ 2.49GB)[6/7] Compiling methods...    [**********]                                                             (112.1s @ 2.00GB)[7/7] Creating image...                                                                                 (53.6s @ 2.85GB)  33.97MB (51.08%) for code area:    48,119 compilation units  29.90MB (44.97%) for image heap:  353,714 objects and 118 resources   2.63MB ( 3.96%) for other data  66.50MB in total------------------------------------------------------------------------------------------------------------------------Top 10 packages in code area:                               Top 10 object types in image heap:   1.63MB sun.security.ssl                                     7.29MB byte[] for code metadata   1.04MB java.util                                            3.64MB java.lang.Class 829.18KB java.lang.invoke                                     3.41MB java.lang.String 717.16KB com.sun.crypto.provider                              2.86MB byte[] for general heap data 558.99KB org.apache.catalina.core                             2.82MB byte[] for java.lang.String 519.74KB org.apache.tomcat.util.net                           1.29MB com.oracle.svm.core.hub.DynamicHubCompanion 491.53KB org.apache.coyote.http2                            878.75KB byte[] for reflection metadata 476.53KB java.lang                                          775.14KB byte[] for embedded resources 467.23KB c.s.org.apache.xerces.internal.impl.xs.traversers  664.31KB java.lang.String[] 461.61KB sun.security.x509                                  619.31KB java.util.HashMap$Node  26.53MB for 628 more packages                                5.45MB for 3069 more object types------------------------------------------------------------------------------------------------------------------------                       189.1s (21.7% of total time) in 60 GCs | Peak RSS: 4.14GB | CPU load: 8.45------------------------------------------------------------------------------------------------------------------------Produced artifacts: /opt/springboot3/target/springboot3 (executable) /opt/springboot3/target/springboot3.build_artifacts.txt (txt)========================================================================================================================Finished generating 'springboot3' in 14m 28s.[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:  14:40 min[INFO] Finished at: 2024-11-16T00:59:34+08:00[INFO] ------------------------------------------------------------------------

编译打包完成

编译完成,得到Linux上的可执行文件

运行可执行文件,并打开浏览器访问测试,功能正常

至此,Linux平台上原生镜像编译就完成了。

4.注意事项

  1. 不是所有Java代码都支持原生编译打包,例如代码中含有反射创建对象,反射调用方法等操作会导致AOT损失动态能力,如果编译需要额外处理,需要提前告知GraalVM未来会反射调用哪些方法或构造器,例如springboot就提供了一些注解,保证AOT编译时功能正常,但不是所有框架都像Spring那样适配了AOT

  2. 配置文件不能放进可执行文件,也需要额外处理,可以在程序代码改为相对路径读取配置文件。

Nginx防止目录穿越

2024年11月7日 00:00

在 Nginx 中防止目录穿越攻击(Directory Traversal)可以通过以下几种方法:

1. 配置 location 块,限制访问特定目录

使用 location 块指定哪些目录可以被访问,确保只允许访问特定的目录,而不允许访问上级目录。比如,假设你希望用户只能访问网站的 /var/www/html 目录,可以使用以下配置:

location / {    root /var/www/html;    try_files $uri $uri/ =404;}

待续

Java线程的状态

2024年11月3日 00:00

JDK 1.5 前线程状态

线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程被阻塞,正在等待锁的释放。
Dead终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之前

JDK 1.5 后线程状态

java.lang.Thread.State

public enum State {    /**     * Thread state for a thread which has not yet started.     */    NEW,    /**     * Thread state for a runnable thread.  A thread in the runnable     * state is executing in the Java virtual machine but it may     * be waiting for other resources from the operating system     * such as processor.     */    RUNNABLE,    /**     * Thread state for a thread blocked waiting for a monitor lock.     * A thread in the blocked state is waiting for a monitor lock     * to enter a synchronized block/method or     * reenter a synchronized block/method after calling     * {@link Object#wait() Object.wait}.     */    BLOCKED,    /**     * Thread state for a waiting thread.     * A thread is in the waiting state due to calling one of the     * following methods:     * <ul>     *   <li>{@link Object#wait() Object.wait} with no timeout</li>     *   <li>{@link #join() Thread.join} with no timeout</li>     *   <li>{@link LockSupport#park() LockSupport.park}</li>     * </ul>     *     * <p>A thread in the waiting state is waiting for another thread to     * perform a particular action.     *     * For example, a thread that has called <tt>Object.wait()</tt>     * on an object is waiting for another thread to call     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on     * that object. A thread that has called <tt>Thread.join()</tt>     * is waiting for a specified thread to terminate.     */    WAITING,    /**     * Thread state for a waiting thread with a specified waiting time.     * A thread is in the timed waiting state due to calling one of     * the following methods with a specified positive waiting time:     * <ul>     *   <li>{@link #sleep Thread.sleep}</li>     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>     *   <li>{@link #join(long) Thread.join} with timeout</li>     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>     * </ul>     */    TIMED_WAITING,    /**     * Thread state for a terminated thread.     * The thread has completed execution.     */    TERMINATED;}
线程状态中文名称描述
New新建刚创建的线程,还未启动。
Runnable可运行线程可以运行,可能在等待 CPU 调度。
Blocked阻塞线程尝试获取锁失败,被阻塞,等待锁释放。
Waiting等待线程进入等待状态,等待其他线程显式唤醒,通常由 Object.wait() 引起。
Timed Waiting计时等待线程等待指定时间后自动唤醒,由 Thread.sleep()wait(time) 引起。
Terminated终止线程执行完成或异常终止,已进入结束状态。

jdk1.5之后

Nginx防盗链设置

2024年10月27日 00:00

1.防止盗链

要防止特定路径下的图片被盗链,可以在Nginx配置中针对图片文件的请求进行防护。以下是一个示例配置,假设你的图片存放在/img/路径下:

server {    listen 80;    server_name *.liuzijian.com;    location /img/ {        valid_referers none blocked *.liuzijian.com;        if ($invalid_referer) {            return 403;  # 返回403 Forbidden        }        alias /public/img/;            }}

注意:为防止目录穿越,alias使用时,location /img/需要使用/闭合,不可写成location /img,以下同理。

解释:

  • location /img/ 仅针对/img/路径下的请求。
  • valid_referers 指令确保只有来自你的域名的请求可以访问这些图片。

重新加载Nginx配置后,这将有效防止其他网站直接链接到你存放在/img/路径下的图片。

2.允许例外

如果你希望在某个特定图片上允许盗链,可以进行更细粒度的控制。

server {    listen 80;    server_name *.liuzijian.com;    # 允许盗链的特定图片    location = /img/logo.jpg {        # 允许所有Referer        add_header Access-Control-Allow-Origin *;        alias /img/logo.jpg;    }    # 其他图片路径的防盗链设置    location /img/ {        valid_referers none blocked   *.liuzijian.com;        if ($invalid_referer) {            return 403;  # 返回403 Forbidden        }        alias /public/img/;                   }}

解释:

  • location = /img/logo.jpg 允许对特定图片的访问,无论Referer如何。
  • add_header Access-Control-Allow-Origin *; 可以使该图片支持跨域访问(如果需要)。

这样配置后,只有logo.jpg会被允许盗链,而其他图片则受到保护。请根据需要调整文件路径和配置。

3.阻止某个目录下特定格式图片被访问

location ~* ^/img/.*\.(jpg|png|gif)$ {    deny all;}

解释:

这段 Nginx 配置的作用是禁止访问特定路径下的图片文件。具体来说,它禁止了 /img/ 目录下的所有 .jpg.png.gif 文件的访问。

~*: 这是一个正则表达式匹配操作符,用于进行不区分大小写的正则匹配~ 表示大小写敏感的正则匹配,而 ~* 表示不区分大小写。

^/img/.*\.(jpg|png|gif)$: 这是匹配请求路径的正则表达式。具体解析如下:

  • ^/img/: 以 /img/ 开头的路径。
  • .*: 匹配零个或多个任意字符(即匹配 /img/ 目录下的所有文件及子目录)。
  • \.(jpg|png|gif): 匹配以 .jpg.png.gif 结尾的文件名。注意:\. 是转义字符,表示字面上的点号 .
  • $: 匹配字符串的结尾,确保正则匹配到文件扩展名的结尾。

总结来说,这个正则表达式会匹配所有位于 /img/ 路径下,且扩展名为 .jpg.png.gif 的文件(不区分大小写)。

deny all;

  • deny all;: 这条指令表示拒绝所有人访问符合上述 location 条件的文件。也就是说,任何请求访问 /img/ 目录下的 .jpg.png.gif 文件,都会被返回一个 403 Forbidden 错误,禁止访问。

使用python将excel表格转换为SQL INSERT

2024年10月16日 10:00
import pandas as pd# 读取Excel文件file_path = 'D:/1.xlsx'  # 替换为你的文件路径sheet_name = '1'  # 替换为你的工作表名称table_name = 'your_table_name'  # 替换为你的数据库表名称# 使用pandas读取Excel数据df = pd.read_excel(file_path, sheet_name=sheet_name)# 获取表的列名columns = ', '.join(df.columns)# 将每行数据转换为SQL INSERT语句insert_statements = []for index, row in df.iterrows():    # 将每行数据转换为元组格式    values = ', '.join([f"'{str(value)}'" for value in row.values])    sql = f"INSERT INTO {table_name} ({columns}) VALUES ({values});"    insert_statements.append(sql)# 输出SQL INSERT语句for statement in insert_statements:    print(statement)

使用python压缩图片

2024年10月15日 20:30
  1. 首先Linux上面要安装一些软件包
yum install -y libjpeg-develyum install -y zlib-develyum install -y libjpeg libtiff freetype 
  1. 其次需要安装python的依赖
pip3 install tinifypip3 install Pillow
  1. 编写并运行以下代码
import osfrom PIL import Imageimport tinifyfrom concurrent.futures import ThreadPoolExecutor# 配置部分input_folder = '/img'  # 要压缩的图片文件夹whitelist = {'example1.jpg', 'important_image.png'}  # 白名单中的文件quality = 75  # 压缩质量def compress_image(img_path, quality):    """    压缩单张图片并覆盖原文件。        :param img_path: 图片路径    :param quality: 压缩质量    """    try:        img = Image.open(img_path)        img.save(img_path, optimize=True, quality=quality)        print(f"Compressed and saved in place: {img_path}")    except Exception as e:        print(f"Failed to compress {img_path}: {e}")def compress_images_in_place(input_folder, whitelist=None, quality=75):    """    使用 Pillow 压缩图片并覆盖原文件,支持白名单功能。使用多线程加速处理。        :param input_folder: 原始图片文件夹路径    :param whitelist: 白名单列表,包含不希望被压缩的图片文件名(可选)    :param quality: 压缩质量,默认75    """    if whitelist is None:        whitelist = set()    # 创建一个 ThreadPoolExecutor 来处理压缩任务    with ThreadPoolExecutor() as executor:        futures = []        for root, dirs, files in os.walk(input_folder):            for file in files:                if file in whitelist:                    print(f"Skipping (whitelisted): {file}")                    continue                if file.lower().endswith(('.jpg', '.jpeg', '.png')):                    img_path = os.path.join(root, file)                    futures.append(executor.submit(compress_image, img_path, quality))        # 等待所有任务完成        for future in futures:            future.result()# 调用压缩函数compress_images_in_place(input_folder, whitelist, quality)

利用Python实现Hexo站点的持续集成

2024年10月15日 20:00

引言

Hexo博客编写完文章需要从头构建并重新上传生成的静态文件到服务器,不能增量更新十分不便,每次上传的文件也越来越大,于是一个比较好的解决办法是在提交文章、资源和配置后,让服务器自动去从git远程仓库拉取最新的提交到服务器上的本地git仓库中,并部署。使用Jenkins等持续集成工具比较繁琐也消耗资源,于是我选择用python编写一个基于flask的webhook脚本来实现。

具体实现流程是:文章提交到git代码托管平台的远程仓库后,远程仓库发送HTTP回调请求给服务器上的webhook脚本,脚本程序根据回调请求的参数,先调用git拉取远程仓库最新提交内容,再去调用npm构建和部署最新版hexo博客到nginx下。

这里的git代码托管平台我选择Gitee:https://gitee.com/

1. 安装pip和python

首先,确保你已经安装了python。如果没有安装,可以使用以下命令来安装python和pip:

1.1 检查python版本

python3 --version

如果你已经安装了python 3.x版本,可以跳过安装python的步骤。否则,继续安装:

1.2 安装python3

sudo yum install python3 -y  # 适用于 CentOS 或其他 RHEL 系统

1.3 安装pip

安装pip的方法:

sudo yum install python3-pip -y  # CentOS/RHEL 系统

安装完成后,确认pip是否已经成功安装:

pip3 --version

2. 使用pip安装依赖

一旦 pip 安装好,你可以使用以下命令来安装需要的库:

pip3 install flask gitpython

3.编写脚本

vim webhook.py
import osimport subprocessfrom flask import Flask, request, jsonifyimport gitapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/path/to/your/hexo/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)                return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "": #这里改成你设置的密码        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 拉取代码并构建    if pull_code() and build_hexo():        return jsonify({"message": "Hexo build success"}), 200    else:        return jsonify({"message": "Failed to pull or build"}), 500if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码优化,加入线程控制,防止webhook链接被并发调用后,两个hook任务线程同时执行出现安全问题。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码还可以进一步优化,只对master分支提交推送进行触发构建。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 解析推送数据    payload = request.get_json()    if not payload:        return jsonify({"message": "Invalid payload"}), 400    # 检查是否是 master 分支的推送    ref = payload.get("ref")    if ref != "refs/heads/master":        return jsonify({"message": "Not a master branch push, ignored"}), 200    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

4.执行脚本

执行前,服务器还需要安装好node、npm、git并配置好环境变量

nohup python3 webhook.py &

5.配置hook到gitee

设置好签名(密码),设置回调地址,勾选两项

除此外,还要将自己gitee账户相匹配的ssh密钥设置在服务器的上用于拉取我们私有仓库的内容。

Redis安装

2024年10月15日 00:00

此处使用64位的Rocky Linux release 9.5环境编译安装Redis-7.2.6,Redis要发挥出最佳性能需要安装运行在Linux系统

1.下载

从官方GitHub地址 https://github.com/redis 下载7.2.6版本源码到服务器

cd /optwget  https://github.com/redis/redis/archive/refs/tags/7.2.6.tar.gz

2.编译安装

解压下载的tar包后,切换到解压后的目录redis-7.2.6里面,src就是redis的源代码,redis.conf是redis配置文件,sentinel.conf和哨兵模式配置有关,Makefile用于编译安装redis

[root@localhost opt]# cd redis-7.2.6/[root@localhost redis-7.2.6]# lltotal 252-rw-rw-r--.  1 root root  22388 Oct  3 03:13 00-RELEASENOTES-rw-rw-r--.  1 root root     51 Oct  3 03:13 BUGS-rw-rw-r--.  1 root root   5027 Oct  3 03:13 CODE_OF_CONDUCT.md-rw-rw-r--.  1 root root   2634 Oct  3 03:13 CONTRIBUTING.md-rw-rw-r--.  1 root root   1487 Oct  3 03:13 COPYING-rw-rw-r--.  1 root root     11 Oct  3 03:13 INSTALL-rw-rw-r--.  1 root root   6888 Oct  3 03:13 MANIFESTO-rw-rw-r--.  1 root root    151 Oct  3 03:13 Makefile-rw-rw-r--.  1 root root  22607 Oct  3 03:13 README.md-rw-rw-r--.  1 root root   1695 Oct  3 03:13 SECURITY.md-rw-rw-r--.  1 root root   3628 Oct  3 03:13 TLS.mddrwxrwxr-x.  8 root root    133 Oct  3 03:13 deps-rw-rw-r--.  1 root root 107512 Oct  3 03:13 redis.conf-rwxrwxr-x.  1 root root    279 Oct  3 03:13 runtest-rwxrwxr-x.  1 root root    283 Oct  3 03:13 runtest-cluster-rwxrwxr-x.  1 root root   1772 Oct  3 03:13 runtest-moduleapi-rwxrwxr-x.  1 root root    285 Oct  3 03:13 runtest-sentinel-rw-rw-r--.  1 root root  14700 Oct  3 03:13 sentinel.confdrwxrwxr-x.  4 root root   8192 Oct  3 03:13 srcdrwxrwxr-x. 11 root root   4096 Oct  3 03:13 testsdrwxrwxr-x.  9 root root   4096 Oct  3 03:13 utils

编译安装前,要先安装必要的软件包

yum install -y make gcc

要保证gcc版本 >= 4.8.5

[root@localhost local]# gcc -vUsing built-in specs.COLLECT_GCC=gccCOLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/11/lto-wrapperOFFLOAD_TARGET_NAMES=nvptx-noneOFFLOAD_TARGET_DEFAULT=1Target: x86_64-redhat-linuxConfigured with: ../configure --enable-bootstrap --enable-host-pie --enable-host-bind-now --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.rockylinux.org/ --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-plugin --enable-initfini-array --without-isl --enable-multilib --with-linker-hash-style=gnu --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_64=x86-64-v2 --with-arch_32=x86-64 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1Thread model: posixSupported LTO compression algorithms: zlib zstdgcc version 11.5.0 20240719 (Red Hat 11.5.0-2) (GCC) 

在解压后的redis-7.2.6目录下执行以下命令,编译安装,将redis安装到/usr/local/redis/bin这个目录下

make PREFIX=/usr/local/redis install

编译完成,切换到/usr/local/redis/bin目录,就会看到编译好的redis二进制文件了,里面包含几个命令

  • redis-benchmark 性能测试工具
  • redis-check-aof 修复有问题的AOF文件
  • redis-check-dump 修复有问题的dump.rdb文件
  • redis-cli 客户端,操作入口
  • redis-sentinel redis集群使用
  • redis-server redis服务器启动命今
[root@localhost redis-7.2.6]# cd /usr/local/redis/bin[root@localhost bin]# lltotal 28496-rwxr-xr-x. 1 root root  6446056 Feb  4 13:14 redis-benchmarklrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-check-aof -> redis-serverlrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-check-rdb -> redis-server-rwxr-xr-x. 1 root root  7030624 Feb  4 13:14 redis-clilrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-sentinel -> redis-server-rwxr-xr-x. 1 root root 15699832 Feb  4 13:14 redis-server

返回redis-7.2.6目录,将目录下的redis.conf拷贝到安装目标位置/usr/local/redis/bin,这个是redis的配置文件

cp redis.conf  /usr/local/redis/bin

3.运行测试

使用redis.conf配置启动redis

./redis-server ./redis.conf 
[root@localhost bin]# ./redis-server ./redis.conf5837:C 04 Feb 2025 13:15:52.211 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.5837:C 04 Feb 2025 13:15:52.212 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5837:C 04 Feb 2025 13:15:52.213 * Redis version=7.2.6, bits=64, commit=00000000, modified=0, pid=5837, just started5837:C 04 Feb 2025 13:15:52.213 * Configuration loaded5837:M 04 Feb 2025 13:15:52.214 * Increased maximum number of open files to 10032 (it was originally set to 1024).5837:M 04 Feb 2025 13:15:52.215 * monotonic clock: POSIX clock_gettime                _._                                                             _.-``__ ''-._                                                   _.-``    `.  `_.  ''-._           Redis 7.2.6 (00000000/0) 64 bit  .-`` .-```.  ```\/    _.,_ ''-._                                   (    '      ,       .-`  | `,    )     Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379 |    `-._   `._    /     _.-'    |     PID: 5837  `-._    `-._  `-./  _.-'    _.-'                                    |`-._`-._    `-.__.-'    _.-'_.-'|                                   |    `-._`-._        _.-'_.-'    |           https://redis.io         `-._    `-._`-.__.-'_.-'    _.-'                                    |`-._`-._    `-.__.-'    _.-'_.-'|                                   |    `-._`-._        _.-'_.-'    |                                    `-._    `-._`-.__.-'_.-'    _.-'                                         `-._    `-.__.-'    _.-'                                                 `-._        _.-'                                                         `-.__.-'                                               5837:M 04 Feb 2025 13:15:52.224 * Server initialized5837:M 04 Feb 2025 13:15:52.224 * Ready to accept connections tcp

出现这个界面就说明启动成功了。

4.简单设置

vim redis.conf配置文件,进行以下修改,完成后重启redis-server

1.默认daemonize no 改为 daemonize yes,让redis从后台启动。

# By default Redis does not run as a daemon. Use 'yes' if you need it.# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.# When Redis is supervised by upstart or systemd, this parameter has no impact.daemonize yes

2.默认protected-mode yes 改为 protected-mode no,关闭保护模式,让其他服务(例如spring-boot后端服务)可以连接到这个redis。

# Protected mode is a layer of security protection, in order to avoid that# Redis instances left open on the internet are accessed and exploited.## When protected mode is on and the default user has no password, the server# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address# (::1) or Unix domain sockets.## By default protected mode is enabled. You should disable it only if# you are sure you want clients from other hosts to connect to Redis# even if no authentication is configured.protected-mode no

3.默认bind 127.0.0.1改为直接注释掉(默认bind 127.0.0.1只能本机访问)或改成0.0.0.0,否则影响远程IP连接

# By default, if no "bind" configuration directive is specified, Redis listens# for connections from all available network interfaces on the host machine.# It is possible to listen to just one or multiple selected interfaces using# the "bind" configuration directive, followed by one or more IP addresses.# Each address can be prefixed by "-", which means that redis will not fail to# start if the address is not available. Being not available only refers to# addresses that does not correspond to any network interface. Addresses that# are already in use will always fail, and unsupported protocols will always BE# silently skipped.## Examples:## bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6# bind * -::*                     # like the default, all available interfaces## ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the# internet, binding to all the interfaces is dangerous and will expose the# instance to everybody on the internet. So by default we uncomment the# following bind directive, that will force Redis to listen only on the# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis# will only be able to accept client connections from the same host that it is# running on).## IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES# COMMENT OUT THE FOLLOWING LINE.## You will also need to set a password unless you explicitly disable protected# mode.# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~bind 0.0.0.0

4.添加redis密码,requirepass一行的注释打开,并将默认密码改为requirepass 自己设置的密码,设置密码更加安全防止被黑客利用攻击。

# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility# layer on top of the new ACL system. The option effect will be just setting# the password for the default user. Clients will still authenticate using# AUTH <password> as usually, or more explicitly with AUTH default <password># if they follow the new protocol: both will work.## The requirepass is not compatible with aclfile option and the ACL LOAD# command, these will cause requirepass to be ignored.#requirepass lzj

5.客户端redis-cli

1.客户端连接
使用redis-cli命令进入交互模式。
-a设置密码
-p设置端口,不写默认6379
-h设置主机(没有用到)
进入交互模式后,客户端发送ping指令,服务端返回pong即为连接成功。
使用quit命令断开连接,退出交互模式。

[root@localhost bin]# ./redis-cli -a lzj  -p 6379 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.127.0.0.1:6379> pingPONG127.0.0.1:6379> quit[root@localhost bin]#

如果忘记输入密码,交互模式下无法执行命令,使用auth命令补上密码,即可连接成功。

[root@localhost bin]# ./redis-cli   -p 6379 127.0.0.1:6379> ping(error) NOAUTH Authentication required.127.0.0.1:6379> auth lzjOK127.0.0.1:6379> pingPONG127.0.0.1:6379> 

服务端不设置密码的话,-a, auth都是不需要的。

2.测试存储数据到Redis

127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> get k1"v1"

3.使用客户端命令关闭redis服务器,该命令关闭的是redis-server。

./redis-cli -a lzj  -p 6379 shutdown

6.用Docker安装Redis

docker run -d \--privileged=true \-v /data/redis/conf:/usr/local/etc/redis \-v /data/redis/data:/data \-p 16379:6379 \--name redis7 \redis:7.0.11  \redis-server /usr/local/etc/redis/redis.conf

Nginx设置HTTPS监听

2024年10月13日 00:00

1. 获取 SSL 证书

首先,你需要获取一个 SSL 证书,可以从以下渠道获得:

  • 自签名证书(测试用)
  • 付费证书(如购买的证书)

这里,博主使用这个网站生成 https://ssl.host.mw/certificate/apply

2. 安装证书

安装证书文件和私钥到服务器,通常是 .crt.key 文件。自己确定存放的目录。

3. 配置 Nginx

编辑 Nginx 配置文件,按照自己实际情况来,通常在 /etc/nginx/nginx.conf 中。

(1)配置 HTTP 重定向到 HTTPS

将所有 HTTP 请求重定向到 HTTPS,你可以在 Nginx 配置文件中添加以下代码:

server {    listen 80;    server_name www.liuzijian.com;    # 重定向所有请求到 HTTPS    return 301 https://$host$request_uri;}

(2)配置 HTTPS 服务器

在同一个配置文件中,添加 HTTPS 服务器的配置:

server {    listen 443 ssl;    server_name www.liuzijian.com;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxx/your_domain.crt;    ssl_certificate_key /xxx/your_domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;        location / {        # 配置你的网站根目录        root /var/www/your_domain;        index index.html index.htm;    }    # 代理到其他端口    #location / {    #    proxy_pass http://127.0.0.1:8360;    #}}

4. 检查并重启 Nginx

确保配置文件没有语法错误:

sudo nginx -t

如果没有错误,重启 Nginx:

sudo systemctl restart nginx

5. 测试

通过访问 http://www.liuzijian.comhttps://www.liuzijian.com 测试是否实现了 HTTP 到 HTTPS 的重定向。

firewalld防火墙工具的使用

2024年10月12日 00:00

firewalld防火墙工具的使用

firewalld 是一种动态的防火墙管理工具,广泛用于 Linux 操作系统(如 CentOS、Fedora 和 RHEL)。它提供了一个用户友好的接口,用于配置和管理防火墙规则,支持 IPv4、IPv6、ARP 和网络桥接。

firewalld 的基本概念

  • 区域(Zones)firewalld 使用区域来定义不同的信任级别。每个区域包含一组规则,用于允许或拒绝网络流量。常见的区域包括 publicprivateinternaldmztrusted

  • 服务(Services)firewalld 支持以服务的方式管理常见的网络服务(如 SSH、HTTP、HTTPS)。你可以使用服务名来快速配置相关的端口和协议。

  • 规则(Rules):可以为每个区域定义规则,包括允许的端口、IP 地址、协议等。

安装 firewalld

在大多数 Linux 发行版上,firewalld 通常默认安装。如果没有安装,可以使用以下命令进行安装:

# CentOS/RHELsudo yum install firewalld# Ubuntu/Debiansudo apt install firewalld

启动和管理 firewalld

  1. 启动 firewalld

    sudo systemctl start firewalld
  2. 设置开机自启

    sudo systemctl enable firewalld
  3. 检查 firewalld 状态

    sudo systemctl status firewalld

常用命令和配置方法

以下是一些常用的 firewalld 命令和配置方法:

1. 查看当前区域和规则

  • 查看活跃的区域

    sudo firewall-cmd --get-active-zones
  • 查看某个区域的详细信息

    sudo firewall-cmd --zone=public --list-all

2. 区域管理

  • 设置默认区域

    sudo firewall-cmd --set-default-zone=public
  • 添加新区域

    sudo firewall-cmd --permanent --new-zone=myzone
  • 删除区域

    sudo firewall-cmd --permanent --delete-zone=myzone

3. 端口管理

  • 开放端口

    sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
  • 关闭端口

    sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent
  • 开启端口转发

    ## 打开端口转发,监听9736端口,并将请求转发到10.53.234.104的16379端口(转发到本机时,需要写本机地址,而不能是127.0.0.1)firewall-cmd --zone=public \--add-forward-port=port=9736:proto=tcp:toport=16379:toaddr=10.53.234.104 \--permanent 
  • 关闭端口转发

    ## 移除端口转发firewall-cmd --zone=public \--remove-forward-port=port=9736:proto=tcp:toport=16379:toaddr=10.53.234.104 \--permanent
  • 重新加载配置(使修改生效):

    sudo firewall-cmd --reload

4. 服务管理

  • 开放服务

    sudo firewall-cmd --zone=public --add-service=http --permanent
  • 关闭服务

    sudo firewall-cmd --zone=public --remove-service=http --permanent
  • 查看已开启的服务

    sudo firewall-cmd --zone=public --list-services

5. IP 地址和地址段管理

  • 允许特定 IP 地址

    sudo firewall-cmd --zone=public --add-source=192.168.1.100 --permanent
  • 删除特定 IP 地址

    sudo firewall-cmd --zone=public --remove-source=192.168.1.100 --permanent
  • 允许 IP 地址段

    sudo firewall-cmd --zone=public --add-source=192.168.1.0/24 --permanent

6. 规则管理

  • 添加富规则(Rich Rules):

    sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100" accept' --permanent
  • 删除富规则

    sudo firewall-cmd --zone=public --remove-rich-rule='rule family="ipv4" source address="192.168.1.100" accept' --permanent

总结

firewalld 是一个灵活且强大的防火墙管理工具,能够通过区域、服务和富规则等方式来简化防火墙配置。通过上述命令和配置方法,您可以轻松地管理 Linux 系统的网络流量,增强系统的安全性。

MySQL5.7x 主从复制

2024年10月11日 00:00

在MySQL中,主从复制(Master-Slave Replication)是一种常用的数据库复制技术,用于将主服务器(Master)的数据实时复制到一个或多个从服务器(Slave)。这有助于负载均衡、备份和高可用性。

前提条件

  • 主服务器从服务器的MySQL版本应兼容(最好相同版本)。
  • 所有服务器时钟应该同步,可以使用NTP确保时间一致性。
  • 从服务器初始时的数据要与主服务器一致(可以通过备份恢复的方式实现)。

一、配置主服务器(Master)

  1. 编辑主服务器的MySQL配置文件
    打开MySQL主服务器的配置文件 my.cnf 。

    添加或修改以下内容:

    [mysqld]server-id = 1                # 唯一的服务器ID,任意正整数,但需要在集群中唯一log-bin = mysql-bin           # 启用二进制日志,确保日志记录所有数据变更binlog-do-db = my_database    # 要复制的数据库(可以指定多个,也可以省略)
    • server-id:每个服务器都需要有唯一的server-id
    • log-bin:启用二进制日志,主从复制就是基于二进制日志的。
    • binlog-do-db:指定要复制的数据库,可以指定多个数据库。省略这行会复制所有数据库。

    重启MySQL服务:

    sudo systemctl restart mysqld
  2. 创建用于复制的用户
    在主服务器上登录MySQL并创建一个用于复制的用户。

    CREATE USER 'slave'@'%' IDENTIFIED BY 'password';GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';FLUSH PRIVILEGES;

    这里创建了一个名为slave的用户,密码为password,并授予了复制权限。

  3. 获取主服务器的状态信息
    在主服务器上运行以下命令,记录输出的信息:

    SHOW MASTER STATUS;

    输出类似以下内容:

    +------------------+----------+--------------+------------------+| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |+------------------+----------+--------------+------------------+| mysql-bin.000001 | 12345    | my_database  |                  |+------------------+----------+--------------+------------------+
    • File:二进制日志的文件名。
    • Position:当前日志的偏移量。这两个值将在配置从服务器时使用。

二、配置从服务器(Slave)

  1. 编辑从服务器的MySQL配置文件

    打开从服务器的配置文件my.cnf,添加或修改如下内容:

    [mysqld]server-id = 2             # 唯一的服务器ID,与主服务器不同relay-log = mysql-relay   # 中继日志,用于存储从主服务器接收到的日志

    重启从服务器的MySQL服务:

    sudo systemctl restart mysqld
  2. 初始化从服务器

    登录从服务器的MySQL,并执行以下步骤来设置复制:

    CHANGE MASTER TO    MASTER_HOST='master_ip_address',    MASTER_USER='replicator',    MASTER_PASSWORD='password',    MASTER_LOG_FILE='mysql-bin.000001',  -- 从SHOW MASTER STATUS获取的日志文件名    MASTER_LOG_POS=12345;                -- 从SHOW MASTER STATUS获取的偏移量

    在这里,master_ip_address是主服务器的IP地址,MASTER_LOG_FILEMASTER_LOG_POS是前面SHOW MASTER STATUS命令中看到的值。

  3. 启动复制

    在从服务器上启动复制进程:

    START SLAVE;
  4. 检查复制状态

    检查从服务器的复制状态,确保复制正常运行:

    SHOW SLAVE STATUS\G;

    查看输出中的两个关键字段:

    • Slave_IO_Running: Yes
    • Slave_SQL_Running: Yes

    如果这两个值都是Yes,说明主从复制正常。如果不是,可以检查错误信息并进行排查。

三、测试主从复制

  1. 在主服务器上创建一个测试表或插入数据:

    USE my_database;CREATE TABLE test_table (id INT PRIMARY KEY, data VARCHAR(50));INSERT INTO test_table VALUES (1, 'Test Data');
  2. 在从服务器上检查该表是否存在以及数据是否同步:

    USE my_database;SELECT * FROM test_table;

    如果能看到相同的表和数据,说明主从复制配置成功。

Git

2024年9月24日 15:00

Git基本操作

1. 设置签名

  • 形式:用户名 + 邮箱地址

  • 级别

    • 系统用户:当前操作系统的用户内有效
    • 仓库级别:仅在本地仓库内有效
  • 作用范围:就近原则

    • 设置了仓库级别,就不使用系统级别
    • 只有系统级别存在时,才使用系统用户级别
  • 至少设置一个级别,否则无法正常工作

配置命令

  • 仓库级别

    git config user.name 用户名git config user.email 邮箱
  • 系统级别

    git config --global user.name 用户名git config --global user.email 邮箱

配置保存位置

  • 系统级别{home}\.gitconfig
  • 仓库级别{repo}\.git\config

2. 暂存文件

git add "文件名"  # 存放文件到暂存区

3. 撤回暂存区文件

git rm --cached <file>  # 将文件由暂存区撤回

4. 提交文件

git commit -m "提交信息"  # 提交一个文件

5. 移除版本控制

git rm -r --cached  # 移除版本控制,提交后生效

新建文件提交

  • 对于新建的文件,必须 git add 进入暂存区。
  • 如果之前已经添加进去了,修改后既可以先添加暂存区再提交,也可以直接:
    git commit -a -m "message"  # 直接提交

6. 查看版本历史

git log  # 查看版本历史纪录git log --oneline  # 简略显示git log --pretty=oneline  # 显示在一行,可读性强git reflog  # 显示切换到哪个版本的记录

日志多屏显示

  • 空格:向下翻页
  • b:向上翻页
  • q:退出

7. 版本回退

git reset --hard 索引值  # 强制回退到指定版本

使用符号回退

  • ^:只能回退
    git reset --hard HEAD^  # 当前版本前退一步
  • 波浪线:表示后退步数
    git reset --hard HEAD~3  # 前退三步

reset 的区别

  • soft:仅在本地库移动 HEAD 指针,不触碰暂存区和工作区
  • mixed:本地库移动指针,重置暂存区
  • hard:本地库移动指针,暂存区和工作区均会被操作

8. 文件差异

git diff 文件名  # 比较工作区与暂存区的差异git diff HEAD 文件名  # 比较工作区与本地库中的历史版本差异
  • 不带文件名会比较多个文件。

9. 分支管理

  • 查看所有分支

    git branch -a  # 查看所有分支git branch -r  # 查看远程分支git branch  # 查看本地分支
  • 删除分支

    git branch -D 分支名
  • 新建分支

    git branch 分支名
  • 切换分支

    git checkout 分支名
  • 创建并切换到新分支

    git checkout -b 分支名
  • 分支合并

    1. 先切换到接受修改的目标分支
    2. 执行:
      git merge 要合并的分支
    3. 如果有冲突,打开文件进行编辑
    4. 解决冲突后:
      git add 文件git commit  # 进行标记解决冲突

10. 远程库

  • 查看所有远程库别名

    git remote -v
  • 添加远程库别名

    git remote add 别名 地址
  • 推送到远程库

    git push 远程库别名 分支名
  • 拉取远程库内容

    git fetch gitlab master  # 拉取到本地,不修改工作区
  • 合并远程库修改

    git pull 远程库别名 分支名  # 拉取并合并

11. Git 原理

  • 哈希算法:使用不可逆算法(如 SHA-1),同一算法得到的长度相同。
  • Git 基于快照进行版本管理,而不是增量式管理。未修改的文件会指向前一个版本。

12. 标签管理

  • 删除标签

    git tag -d test_tag
  • 远端删除标签

    git push origin --tags

基于Hexo实现一个静态的个人博客

2024年9月20日 00:00

引言

Hexo是中国台湾开发者Charlie在2012年创建的一个开源项目,旨在提供一个简单、快速且易于扩展的静态博客生成器。

Hexo的设计理念是轻量级、易用和支持插件扩展,因此它非常适合那些有技术背景的用户,尤其是喜欢使用Markdown和Git进行内容管理的开发者。Hexo使用Node.js构建,并且支持通过主题和插件来扩展功能。

1.初始化Hexo

Hexo基于node.js,所以首先要安装node和npm

1.全局安装hexo

npm install hexo -g

2.生成hexo项目

安装完成后,选择一个目录,执行以下命令,生成一个hexo脚手架

hexo init myblog

然后生成脚手架结构如下

myblog/ ├── node_modules ├── scaffolds │── scripts    ├── source/ │    ├── _posts │    └── _drafts ├── themes ├── _config.landscape.yml ├── _config.yml └── package.json

文件目录解释

  • node_modules不解释
  • scaffolds 存放博客内容模板的地方
  • source 资源文件夹,下面用来放建站需要的各种资源,包括markdown格式的博客原稿,图片,css,js,robots.txt等等
  • source/_posts 博客的”源码”,里面放我们写作的markdown文件,会被hexo引擎按照一定规则转换成html页面
  • source/_drafts 草稿,不适合放在_posts里面打包发布的草稿就放在这里
  • themes 存放第三方主题
  • _config.yml hexo配置文件
  • _config.landscape.yml 默认主题landscape的配置文件
  • package.json不解释

提前剧透:hexo使用过程中,还会经常用到两个文件夹

  • scripts hexo自定义脚本,在构建过程中执行,用于扩展hexo的功能或实现一些特殊需求
  • public 构建生成的静态网站,可以直接使用nginx root反代访问

3.启动预览

修改_config.yml配置文件

# Sitetitle: '我的博客'subtitle: '我的技术博客'description: '分享一些实用的东西'author: 'liuzijian'language: zh-CNtimezone: 'Asia/Shanghai'

然后,运行package.json里面的目标npm run server

直到输出提示Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.以后,访问http://127.0.0.1:4000进行预览,浏览器出现这个界面说明启动成功

4.构建打包

运行npm run build后,根目录就会生成一个public文件夹,就是构建成的静态网站,上传到服务器并使用nginx代理,访问/index.html即可打开博客首页。

除了部署到自己的服务器,还可以通过VercelGitHub Pages等进行部署。

2.整合主题Fluid

hexo默认自带一个叫landscape的主题,根目录下的_config.landscape.yml就是它的配置文件,landscape比较简陋,也不美观,所以很多人选择美观且功能强大的第三方主题,在这里,我使用主题fluid来建站。

集成第三方主题到hexo中非常简单,只需要将主题解压放在theme文件夹,复制主题的配置文件到根目录下,然后到hexo配置文件内切换主题即可。

1.下载主题fluid

项目首页和文档地址: https://hexo.fluid-dev.com/

下载地址: https://github.com/fluid-dev/hexo-theme-fluid/releases

找到最新的1.9.8版本下载连接

https://github.com/fluid-dev/hexo-theme-fluid/archive/refs/tags/v1.9.8.zip

2.集成主题

将压缩包下载下来,解压,压缩包内文件夹名字叫hexo-theme-fluid-1.9.8,这个就是主题,不过要把解压后的hexo-theme-fluid-1.9.8文件夹重命名为fluid,紧接着要再将fluid文件夹里面的_config.yml文件复制一个副本,将副本重命名为_config.fluid.yml,再将_config.fluid.yml剪切到hexo项目根目录下和hexo脚手架自带的的_config.yml文件放在一起,接下来还要再把整个fluid文件夹拷贝到根目录下的themes文件夹内。

⚠️注意: 主题文件夹内的_config.yml一定要创建副本,而不是直接改名,副本_config.fluid.yml剪切走后,原有的_config.yml文件必须原样保留! hexo-theme-fluid-1.9.8文件夹下的_config.yml是主题的配置,与脚手架生成在根目录的hexo配置文件_config.yml是不同的,重命名后的主题配置文件_config.fluid.yml和hexo脚手架自带的配置文件_config.yml放在一起共同作为hexo的配置文件,其他的第三方主题基本也遵循这个方式。关于配置文件的更多解释可以参考Hexo官方文档以及主题的官方文档。
实际上和根目录下脚手架自带的_config.landscape.yml文件是一样的道理,现在换成了别的主题,_config.landscape.yml也就没用了,可以直接删除

myblog/    ├── ... ...    ├── themes    │     └── fluid    ├── _config.fluid.yml    ├── _config.yml    └── ... ...

要想主题生效,还需要在配置文件进行指定,修改_config.yml,找到以下配置

# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: landscape

修改为以下,然后保存

# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: fluid

再次启动npm run server,浏览器预览博客的效果已经变为主题的风格,即为集成主题成功

主题更多玩法,在主题的官网都有文档说明,主题配置文件_config.fluid.yml内也有详细注释,这里不再详细一一介绍,集成其他主题的方式方法同理。

3.部署评论系统Waline

评论系统一般由主题提供支持,支持的评论系统有许许多多,这里我使用的是Waline,并且采用Docker独立部署Waline

Waline的使用部署等可参考官网,其官网地址为: https://waline.js.org/,源码位于GitHub: https://github.com/walinejs/waline.git

1.构建镜像

先用git clone将源码下载到本地,然后进入对应目录,构建镜像。

docker build -t lizheming/waline -f packages/server/Dockerfile .

2.启动Waline服务

使用参数启动镜像,参数根据Waline指定的服务端运行参数而来,这里采用MySQL作为数据存储为例,常见参数有:

  • OAUTH_URL 变量是因为oauth服务也是我自己私有部署的,如果使用waline公共的则不用这个变量
  • IPQPS IP评论频次限制
  • MYSQL_DB 数据库名,使用MySQL部署,需要提前导入数据库脚本,详情查阅waline官网文档
  • MYSQL_USER 不解释
  • MYSQL_PASSWORD 不解释
  • MYSQL_PORT 不解释
  • SERVER_URL 访问waline系统时的地址前缀,因为docker部署由nginx反代,建议设置为反代后的地址
  • COMMENT_AUDIT 评论审核,布尔值
  • SITE_NAME 邮件中展示的网站名
  • SITE_URL 邮件中展示的网站地址
  • AUTHOR_EMAIL 发送者邮件地址
  • SMTP_PASS 邮件服务密码
  • SMTP_USER 邮件用户名
  • SMTP_SERVICE 邮件提供商,具体列表可见官方文档

启动命令示例:

docker run -d \-e OAUTH_URL=https://oauth.liuzijian.com \-e IPQPS=10 \-e MYSQL_DB=waline \-e MYSQL_USER=****** \-e MYSQL_PASSWORD=********** \-e MYSQL_PORT=3306 \-e SERVER_URL=https://waline.liuzijian.com \-e COMMENT_AUDIT=true \-e SITE_NAME="Liu Zijian's Blog"  \-e SITE_URL=https://blog.liuzijian.com  \-e SMTP_PASS=****************  \-e AUTHOR_EMAIL=****@foxmail.com  \-e SMTP_USER=******@foxmail.com  \-e SMTP_SERVICE=QQ  \-p 8360:8360 \--network=host \lizheming/waline

有时,我们选取的服务器配置很低,运行一个MySQL后,内存资源可能就所剩无几,而且我们的个人博客可能初期影响力也不是那么大,评论寥寥无几,使用MySQL等数据库就性价比很低了,因此可以先换成SQLite数据库,SQLite将数据保存在单文件,使用代码本地调用SQLite的API方法实现增删改查,对于初期的评论功能足够用了,使用下面命令就可以启动一个采用SQLite数据库的Waline,Waline官方已经为我们准备好了一个空的waline.sqlite数据文件,里面初始化好了库和表,直接按照官方文档指定的地址下载即可。

  • SQLITE_PATH 本地数据库文件路径,指定到文件夹,不是文件
  • JWT_TOKEN Waline官方要求,我也不知道为什么
docker run -d \-e IPQPS=10 \-e SQLITE_PATH=/opt/sqlite \-e JWT_TOKEN=******** \-e SERVER_URL=https://waline.liuzijian.com \-e COMMENT_AUDIT=true \-e SITE_NAME="Liu Zijian's Blog"  \-e SITE_URL=https://blog.liuzijian.com  \-e SMTP_PASS=****************  \-e AUTHOR_EMAIL=****@foxmail.com  \-e SMTP_USER=******@foxmail.com  \-e SMTP_SERVICE=QQ  \-p 8360:8360 \-v /opt/sqlite_data:/opt/sqlite  \--network=host \lizheming/waline

怎样在fluid主题上使用waline评论,主题文档有说明,不再赘述。

4.采用Nginx部署

页面和评论插件都可以通过nginx根据不同域名进行反代访问,同时https证书也是集成在nginx上的。

主要配置如下:

server {    listen       80;    listen       [::]:80;    server_name  *.liuzijian.com;    return 301 https://$host$request_uri;}## 静态页面server {    listen 443 ssl;    server_name blog.liuzijian.com ;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    location / {        try_files $uri $uri/ $uri.html   =404;        root /opt/blog/public;    }    error_page 404 /404.html;}## 评论插件server {    listen 443 ssl;    server_name    waline.liuzijian.com;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    location / {        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header Host $host;        proxy_set_header X-Real-IP $remote_addr;        proxy_pass http://127.0.0.1:8360;    }}    ## 自动部署hook,选用server {    listen 443 ssl;    server_name hook.liuzijian.com;    # SSL 证书路径,按照实际情况填写    ssl_certificate /xxxxx/domain.cert;    ssl_certificate_key /xxxxxx/domain.key;    # 推荐的 SSL 配置    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers HIGH:!aNULL:!MD5;    # 代理到其他端口    location / {        proxy_pass http://127.0.0.1:5000;    }}

Hexo站点可以采用持续集成的方式部署在服务器,参考:利用Python实现Hexo的持续集成

Docker离线安装

2024年8月4日 00:00
  1. 下载二进制包
    https://download.docker.com/linux/static/stable/x86_64/

  2. 创建启动文件

vi /usr/lib/systemd/system/docker.service
[Unit]Description=Docker Application Container EngineDocumentation=https://docs.docker.comAfter=network-online.target firewalld.serviceWants=network-online.target[Service]Type=notifyExecStart=/usr/bin/dockerdExecReload=/bin/kill -s HUP $MAINPIDLimitNOFILE=infinityLimitNPROC=infinityTimeoutStartSec=0Delegate=yesKillMode=processRestart=on-failureStartLimitBurst=3StartLimitInterval=60s[Install]WantedBy=multi-user.target
  1. 解压可执行文件到/usr/bin/

  2. 加载服务

systemctl daemon-reloadsystemctl restart docker

MySQL数据定义语言

2024年6月9日 00:00

1.库操作

1.1 创建数据库

CREATE DATABASE book

1.2 创建数据库,如果存在

CREATE DATABASE IF NOT EXISTS book

1.3 切换到某个数据库

use book

1.4 更改库字符集

ALTER DATABASE book CHARACTER SET utf8ALTER DATABASE book CHARACTER SET gbk

1.5 删除数据库

DROP DATABASE bookDROP DATABASE IF EXISTS book

2.数据类型

2.1 整形

整形 TINYINT SMALLINT MEDIUMINT INT(INTEGER) BIGINT,对于整形,长度由范围决定,设置的长度用来补零,但必须在建立字段时搭配zerofill,一旦zerofill,默认为无符号,如果不设置无符号,默认是有符号,如果插入数值超出范围,系统会警告,默认插入临界值。

# 设置有无符号create table user( age int UNSIGNED , id int )INSERT INTO `user` VALUES (-9,9)

2.2 浮点数

浮点型小数 float(m,d) double(m,d)
定点型小数 DECIMAL(m,d) ,精度高,最大取值范围与double相同,m 整数部分和小数部分合起来的位数,d 小数点后保留位数,超出范围默认是临界值

2.3 字符型

短文本char VARCHAR,长文本text

CHAR(m),m指最大字符数, 固定长度字符,开的空间与长度有关 ,耗费空间,效率高,可以省略m, m默认是1。
VARCHAR(m) m指最大字符数, 可变长度的字符,开的空间与实际占用有关,节省空间,效率低,不能省略m。

2.4 枚举型

enum('c','b') 掺入的值不在枚举内,默认是空,不区分大小写

2.5 SET

一次可以插入多个值,不区分大小写

2.6 日期值

必须用单引号引起,每个类型作用范围不同

date 1000-01-01 - 9999-12-31
time 838:59:59
datetime 1000-01-01 - 9999-12-31
year 1901 - 2155
TIMESTAMP 1970 - 2038年某一时刻,受时区影响,更能反应实际日期,与MySQL版本和sqlmode关系很大

3.表操作

3.1 创建表

CREATE TABLE book(id INT(10) ,`name` VARCHAR (30),price DOUBLE,authorId INT(10),publishDate DATETIME);CREATE TABLE author(id INT,`name` VARCHAR(20));

3.2 查看表描述

DESC book

3.3 修改列名 类型

ALTER TABLE book CHANGE COLUMN publishDate pubDate datetime

3.4 修改类约束 类型

ALTER TABLE book MODIFY COLUMN pubDate datetime

3.5 添加新列

ALTER TABLE author ADD COLUMN last_name VARCHAR(20)ALTER TABLE author ADD COLUMN first_name VARCHAR(20)

3.6 删除列

ALTER TABLE author DROP COLUMN first_name

3.7 修改表名

ALTER TABLE author RENAME TO `authors`

3.8 删除表

DROP TABLE IF EXISTS `authors`

3.9 复制

仅仅复制结构

CREATE TABLE 1_author LIKE `author`

复制结构和数据

CREATE TABLE 12_author SELECT * FROM `author`

条件复制

CREATE TABLE 12_author SELECT * FROM `author` WHERE id = 1

复制部分列

CREATE TABLE 12_author SELECT `name` FROM `author` WHERE id = 1

复制部分列,不要数据,使用恒不成立

CREATE TABLE 12_author SELECT `name` FROM `author` WHERE 2 = 1CREATE TABLE 12_author SELECT `name` FROM `author` WHERE 0 /*0==false*/

4.约束

6种约束

  1. NOT NULL (非空约束): 确保列不能有 NULL 值。常用于必须填写的字段。
  2. DEFAULT (默认值约束): 如果插入数据时未指定该列的值,则使用默认值填充。
  3. PRIMARY KEY (主键约束): 保证字段值的唯一性和非空,常用于唯一标识表中每一行的字段。
  4. UNIQUE (唯一约束): 确保列中的值唯一,但可以有 NULL 值。
  5. CHECK (检查约束): 验证字段的值是否符合指定条件,但 MySQL 并不支持 CHECK 约束。
  6. FOREIGN KEY (外键约束): 设定两个表之间的关系,确保该字段的值必须来自于主表的关联列。

表级与列级约束:

  • 表级约束: 可以在创建表时定义,除 NOT NULLDEFAULT 之外的所有约束都支持。
  • 列级约束: 语法上支持所有约束,但 MySQL 中外键在列级定义时可能无法有效执行(需要在表级定义才能生效)。

4.1 主键

添加主键

ALTER TABLE stuinfo MODIFY COLUMN id INT PRIMARY KEY-- 支持表级约束的也可以这样ALTER TABLE stuinfo ADD PRIMARY KEY (id)
CREATE TABLE stuinfo(id int ,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT,CONSTRAINT pk/*mysql中,主键改名无效*/ PRIMARY KEY (id))

主键和唯一的对比

都可以保证唯一,主键不能为空,每个表只能有1个,可以两个列自合一起,唯一允许为空,一个表可以有多个,不能多个为空,可以两个列自合一起。

外键,从表设置外键关系,数据类型一致或兼容,主表被关联列必须是一个key,一般是主键或唯一,插入数据时,先插入主表在插入从表,删除数据,先删除从表,再删除主表。

4.2 非空

CREATE TABLE stuinfo(id int ,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT)-- 修改表时添加约束ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NOT NULLALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NULL

4.3 唯一

添加唯一

ALTER TABLE stuinfo ADD UNIQUE (seat)

4.4 外键

添加外键

ALTER TABLE stuinfo ADD FOREIGN KEY (majarId) REFERENCES majar(id)

添加外键 名字

ALTER TABLE stuinfo ADD CONSTRAINT fk_majar FOREIGN KEY (majarId) REFERENCES majar(id)

4.5删除约束

删除非空

ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NULL

不添加条件,就会删除默认约束

ALTER TABLE stuinfo MODIFY COLUMN age 

删除主键

ALTER TABLE stuinfo DROP PRIMARY KEY

先查到,在删除唯一约束

SHOW INDEX FROM stuinfo ALTER TABLE stuinfo DROP INDEX seat

删除外键

ALTER TABLE stuinfo DROP FOREIGN KEY fk_majar

4.6标识列

限制某个字段,又叫自增长列,默认从1开始,必须是一个key,同一张表标识列只能有一个,只能是数值类型

CREATE TABLE stuinfo(id int PRIMARY KEY auto_increment,stuname VARCHAR(20) ,gender CHAR (1) ,seat INT ,age  INT UNSIGNED ,majarId INT)

步长auto_increment_increment 起始值auto_increment_offset,mysql 可以设置步长,但是不能设置起始值

show VARIABLES like '%auto_increment%'

设置删除标识列

ALTER TABLE stuinfo MODIFY COLUMN id int PRIMARY KEY auto_incrementALTER TABLE stuinfo MODIFY COLUMN id int  

Docker与联合文件系统

2024年4月6日 00:00

1. 联合文件系统

  • 概念

    UnionFS(联合文件系统)是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次次的提交来一层一层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。联合文件系统是docker镜像的基础,镜像可以通过分层来实现继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

  • 特性

    一次加载多个文件系统,但在外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

2.docker镜像加载原理

docker的镜像实际上由一层一层文件系统组成,这种层级的文件系统就是UnionFS,bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导和加载kernel,Linux刚启动时会加载bootfs文件系统,在docker镜像最底层是bootfs,这一层与我们典型的Linux/Unix操作系统是一样的,包含boot加载器和内核,当boot加载完成之后,整个内核都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统会卸载bootfs。

rootfs(root file system)在bootfs之上,包含的就是典型Linux系统中/dev, /proc, /bin, /etc 等标准目录和文件,rootfs就是各种不同的操作系统发行版,比如Ubuntu CentOS等。

对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令,工具和程序就可以了,因为底层直接用host的kernel,自己只需要提供rootfs就可以了,由此看见对于不同的Linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以共用bootfs。这样最大的好处就是共享资源,比如多个镜像都从base镜像构建而来,那么宿主机只需要在磁盘上保存一个base镜像,就可以为所有容器服务了,而且镜像的每一层都可以被共享。支持通过拓展现有镜像,创建新的镜像。

docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称之为容器层,容器层之下的都叫镜像层,所有对容器的改动,无论添加删除还是修改文件,都只会发生在容器中,只有容器层是可写的,容器层下面所有镜像层都是只读的。

Docker的网络

2024年1月24日 00:00

Docker的网络用于容器间的互联和通信,以及宿主机端口映射,容器IP变动的时候可以设置网络直接使用服务名进行网络通信而不受影响。类似于虚拟机软件分配IP地址给各个安装的虚拟机以及物理机,虚拟机之间以及虚拟机和物理机之间可以相互通信。

1.查看容器的网络

1.查看容器网络类型

docker inspect命令查看关于Networks的部分

docker inspect 容器ID

当前容器是bridge网络,网关是172.17.0.1, IP是172.17.0.2

"Networks": {              "bridge": {                  "IPAMConfig": null,                  "Links": null,                  "Aliases": null,                  "NetworkID": "2def63006d0705d822ebd7269baf1512be0fc886c40e9047f35635ef21585b33",                  "EndpointID": "4fd79243a84032b34a57a39b0ffc03136bef175498980abdc097117e17f135fe",                  "Gateway": "172.17.0.1",                  "IPAddress": "172.17.0.2",                  "IPPrefixLen": 16,                  "IPv6Gateway": "",                  "GlobalIPv6Address": "",                  "GlobalIPv6PrefixLen": 0,                  "MacAddress": "02:42:ac:11:00:02",                  "DriverOpts": null              }          }

2.查看当前有多少个docker网络

docker network ls
[root@localhost ~]# docker network lsNETWORK ID          NAME                DRIVER              SCOPE2def63006d07        bridge              bridge              localf8b834999d26        host                host                localfd9dc003ef95        lzjnet              bridge              local2ca13d3f5552        none                null                local

3.查看网络的情况

docker network inspect 网络名

网络lzjnet的网络类型是bridge,还有IP等信息

[    {        "Name": "lzjnet",        "Id": "fd9dc003ef956e7b3f01d74b81f5fc6cc04c76ce5144beadc9707dd4a7a41134",        "Created": "2024-11-26T23:12:55.276879774+08:00",        "Scope": "local",        "Driver": "bridge",        "EnableIPv6": false,        "IPAM": {            "Driver": "default",            "Options": {},            "Config": [                {                    "Subnet": "172.18.0.0/16",                    "Gateway": "172.18.0.1"                }            ]        },        "Internal": false,        "Attachable": false,        "Ingress": false,        "ConfigFrom": {            "Network": ""        },        "ConfigOnly": false,        "Containers": {},        "Options": {},        "Labels": {}    }]

2.Docker的网络类型

几种常见的网络类型

网络模式简介
bridge默认为该模式,为每一个容器分配和设置IP,并将容器连接到默认的虚拟网桥docker0
host容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
none容器有独立的network namespace,但并没有对其进行任何网络设置,如分配veth pair和网桥连接,IP等
container新创建的容器不会自动创建自己的网卡和配置IP,而是和一个指定的容器共享IP和端口范围等

2.1 bridge网络

1.主机和容器联网

Docker服务默认会创建一个网桥(上有一个docker0的内部接口),该桥接网络的名称为docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信。

2.容器之间联网

Docker启动一个容器时会根据docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关,因为在同一宿主机内的容器都接入同一网桥,这样容器之间就可以通过容器的Container-IP直接通信。

3.bridge网络的分配机制

docker run的时候,没有指定network的话,默认使用的网桥模式就是bridge(docker0)。在宿主机ifconfig就可以看见docker0和自己创建的网络(eth0, eth1…..),lo代表127.0.0.1。

网桥docker0创建一对对等的虚拟设备接口,一个叫veth,另一个叫eth0,成对匹配。

整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair)

每个容器内部也有一块网卡,每个接口叫eth0,docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。

验证veth-pair:

查看正在运行的Docker容器

[root@localhost ~]# docker psCONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES38188376416b        tomcat              "catalina.sh run"   About a minute ago   Up About a minute   8080/tcp            inspiring_austin

宿主机执行ip addr查看宿主机的网络情况

[root@localhost ~]# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever    inet6 ::1/128 scope host        valid_lft forever preferred_lft forever2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000    link/ether 00:0c:29:44:65:de brd ff:ff:ff:ff:ff:ff    inet 192.168.228.104/24 brd 192.168.228.255 scope global noprefixroute ens33       valid_lft forever preferred_lft forever    inet6 fe80::2ac6:ec4:9411:4da8/64 scope link noprefixroute        valid_lft forever preferred_lft forever3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default     link/ether 02:42:ff:ca:bc:ec brd ff:ff:ff:ff:ff:ff    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0       valid_lft forever preferred_lft forever    inet6 fe80::42:ffff:feca:bcec/64 scope link        valid_lft forever preferred_lft forever15: vethc5f3f03@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default     link/ether 26:9a:2e:de:95:5a brd ff:ff:ff:ff:ff:ff link-netnsid 1    inet6 fe80::249a:2eff:fede:955a/64 scope link        valid_lft forever preferred_lft forever

进入容器,一般需要先更新tomcat容器的yum,再安装工具iproute2

sudo apt-get updatesudo apt-get upgradeapt-get install -y iproute2

然后,在容器内执行ip addr查看容器的网络情况

root@38188376416b:/# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default     link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0       valid_lft forever preferred_lft forever

然后可见宿主机的网卡 15: vethc5f3f03@if14 和容器网卡 14: eth0@if15两两匹配,互互相同

2.2 host网络

直接使用宿主机的IP地址与外界通信,不需要额外进行NAT转换。

容器将不再获得一个独立的网络名称空间,而是和宿主机共用一个网络名称空间,容器将不会虚拟出自己的网卡,而是使用宿主机的IP和端口,容器没有自己的IP。

这种模式下,使用-p端口映射是无效的,也是没有任何意义的。

2.3 none网络

等同于禁用网络功能,不为容器进行任何网络配置,容器没有网卡,路由等信息,只有一个本地回环网卡lo

2.4 container网络

新建的容器和一个已经存在的容器共享一个网络IP配置而不是和宿主机共享,新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP,端口范围等,同样,两个容器除了网络方面,其他的例如文件系统,进程列表等还是隔离的。

需要注意的是:

1.继承和被继承的容器的容器内端口不能是相同的,否则会导致冲突,因为两个容器共享网段和IP

验证:先启动一个tomcat容器tomcat86,该容器内的程序会占用容器的8080,在启动另一个tomcat容器tomcat87和前者共享网络,因为也是基于tomcat镜像,所以同样会占用容器的8080,两个容器端口范围是共享的,导致报错Error response from daemon: conflicting options: port publishing and the container type network mode.

docker run -d --privileged=true -p 8086:8080  --name tomcat86    tomcatdocker run -d --privileged=true -p 8086:8080 --network container:tomcat86  --name tomcat87    tomcat
[root@localhost ~]# docker run -d --privileged=true -p 8086:8080  --name tomcat86    tomcatc8464b5448296b51bc5b4e4598acce709513aa6ac7fac6ef335e5a39e8a785f3[root@localhost ~]# [root@localhost ~]# [root@localhost ~]# docker psCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMESc8464b544829        tomcat              "catalina.sh run"   4 seconds ago       Up 2 seconds        0.0.0.0:8086->8080/tcp   tomcat860bad9102a5a7        alpine              "/bin/sh"           11 minutes ago      Up 11 minutes                                alpine1[root@localhost ~]# [root@localhost ~]# [root@localhost ~]# [root@localhost ~]# docker run -d --privileged=true -p 8086:8080 --network container:tomcat86  --name tomcat87    tomcatdocker: Error response from daemon: conflicting options: port publishing and the container type network mode.See 'docker run --help'.

2.被继承的容器结束运行后,继承它的容器也会失去网络功能,只剩下一个回环网卡lo

验证:先运行一个精简版Linux容器alpine1,在运行一个同样的容器alpine2,指定和alpine1共享网络,查看容器内网络可见网卡名称和IP地址都是完全一样的

docker run -it --name alpine1   alpine /bin/sh
[root@localhost ~]# docker run -it --name alpine1   alpine /bin/sh/ # ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP     link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0       valid_lft forever preferred_lft forever
docker run -it --name alpine2 --network container:alpine1   alpine /bin/sh
[root@localhost ~]# docker run -it --name alpine2 --network container:alpine1   alpine /bin/sh/ # ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP     link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0       valid_lft forever preferred_lft forever

关闭容器alpine1,进入容器alpine2查看IP,发现eth网卡跟着消失了,只剩下lo

[root@localhost ~]# docker stop alpine1alpine1[root@localhost ~]# docker exec -it alpine2 /bin/sh/ # ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever

2.5 自定义网络

为什么要自定义网络?

1.容器间的互联和通信以及端口映射
2.bridge模式网络分配给容器的IP是可能随着容器的启动关闭而导致分配的地址发生变动的,但是Docker自带的bridge网络比较简单,没有内置DNS功能,自定义网络后,容器IP变动的时候可以通过服务名直连网络而不受影响,当用户创建自定义网络时,Docker自动配置了一个内置DNS服务器,这个DNS服务器会根据容器的名称自动解析对应的IP地址。

自定义网络默认也是采用桥接模式bridge,只是功能比默认的那个更强,自然就维护好了容器名和IP的映射关系(DNS)

验证:

创建一个网络lzjnet

docker network create lzjnet

创建容器tomcat1 tomcat2,指定我们自己创建的网络,然后互PING

docker run -it --privileged=true  --network lzjnet   --name alpine81    alpinedocker run -it --privileged=true  --network lzjnet   --name alpine82    alpine
[root@localhost ~]# docker attach alpine81/ # ping alpine82PING alpine82 (172.18.0.3): 56 data bytes64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.889 ms64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.195 ms64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.181 ms64 bytes from 172.18.0.3: seq=3 ttl=64 time=0.167 ms64 bytes from 172.18.0.3: seq=4 ttl=64 time=0.547 ms^C--- alpine82 ping statistics ---5 packets transmitted, 5 packets received, 0% packet lossround-trip min/avg/max = 0.167/0.395/0.889 ms
[root@localhost ~]# docker attach alpine82/ # ping alpine81PING alpine81 (172.18.0.2): 56 data bytes64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.079 ms64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.133 ms64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.169 ms64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.186 ms64 bytes from 172.18.0.2: seq=4 ttl=64 time=0.663 ms64 bytes from 172.18.0.2: seq=5 ttl=64 time=0.260 ms64 bytes from 172.18.0.2: seq=6 ttl=64 time=0.176 ms^C--- alpine81 ping statistics ---7 packets transmitted, 7 packets received, 0% packet lossround-trip min/avg/max = 0.079/0.238/0.663 ms

可以发现可以使用容器名相互PING通

3.总结

Docker可以使用4种网络模式来实现容器之间以及和宿主机的网络通信,自己实现自定义的网络桥接模式功能更强。

❌