移动云ECS初始化、系统配置、中间件和应用部署、网络架构文档

这篇文档以移动云中的几台ECS为例, 记录了ECS的初始化、中间件和应用部署的示例、ECS集群搭建的实例、还有负载均衡的配置实例

0x00 必备组件安装和初始化

在一切开始之前, 首先初始化移动云的云公网IP和NAT配置, 具体参考NAT网关和弹性公网IP配置

0x00-0 初始化额外数据盘

以其中一台ECS为例, 操作系统 Ubuntu2204 LTS, 有100G系统盘和额外挂载的500G云硬盘
查看已经挂载的云硬盘

移动云控制台显示硬盘已经挂载但未被初始化, 使用fdisk -l校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@ecs-hykg-app:/# fdisk -l
Disk /dev/sda: 100 GiB, 107374182400 bytes, 209715200 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x65f2787d

Device Boot Start End Sectors Size Id Type
/dev/sda1 * 2048 209715166 209713119 100G 83 Linux


Disk /dev/sdb: 500 GiB, 536870912000 bytes, 1048576000 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

对硬盘进行分区, fdisk /dev/sdb, 系列命令如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
root@ecs-hykg-app:/# fdisk /dev/sdb

Welcome to fdisk (util-linux 2.37.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xd9d54c85.

Command (m for help): n # n 新建分区
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p # p 主分区
Partition number (1-4, default 1): 1 # 分1个区
First sector (2048-1048575999, default 2048): # 默认2048
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-1048575999, default 1048575999): # 默认最后一个分区位置

Created a new partition 1 of type 'Linux' and of size 500 GiB.

Command (m for help): p # 查看当前分区
Disk /dev/sdb: 500 GiB, 536870912000 bytes, 1048576000 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xd9d54c85

Device Boot Start End Sectors Size Id Type
/dev/sdb1 2048 1048575999 1048573952 500G 83 Linux

Command (m for help): wq # 退出
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

创建挂载点: mkdir /data

磁盘格式化为ext4: mkfs.ext4 /dev/sdb1

挂载: mount /dev/sdb1 /data/

持久化挂载, 避免重启后失效: 需要将配置文件写入fstab

获取需要操作盘的UUID信息

1
2
blkid /dev/sdb1
/dev/sdb1: UUID="f704f222-ba2d-41df-b1d8-f31715d9ba67" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="d9d54c85-01"

编辑/etc/fstab文件

1
2
3
# 将上面获取的 UUID 和计划挂载的路径写入
vim /etc/fstab
UUID=f704f222-ba2d-41df-b1d8-f31715d9ba67 /data ext4 defaults 1 1

大容量硬盘可以使用parted分区, 使用GPT格式分区表

完整教程参考移动云磁盘初始化指南

或者使用初始化脚本LinuxVMDiskAutoInitialize2.sh

0x00-1 初始化SSH允许远程登录

安装 openssh-server 服务, 配置允许 root 用户远程登录, 使用强密码, 监听 22 端口

由于需要映射 SSH 服务到公网, 密码验证的安全性较差, 所以需要额外配置安全组规则, 使用白名单模式只放行 hysz 的公网IP

配置安全组规则

0x01 ECS-HYKG-APP的中间件和应用部署示例

ECS-HYKG-APP这台ECS为例, 介绍下主应用服务器的部署逻辑

0x01-0 直接部署服务——以Nacos的部署为例

最主要需要注意的事项是相关部署的文件或者程序需要在/home/归档, 便于以后查阅

实机部署Nacos 2.0.3, 可以参考Nacos官方文档

部署程序依赖的依赖的JDK 1.8+环境, 验证安装

1
2
3
4
5
6
7
8
root@ecs-hykg-app:~# apt install openjdk-8-jdk
# ···
root@ecs-hykg-app:~# which java
/usr/bin/java
root@ecs-hykg-app:~# java -version
openjdk version "1.8.0_422"
OpenJDK Runtime Environment (build 1.8.0_422-8u422-b05-1~22.04-b05)
OpenJDK 64-Bit Server VM (build 25.422-b05, mixed mode)

下载 Nacos 安装包, 安装到/opt/nacos-2.0.3路径, 软链接到/home

使用程序中自带的SQL Schema脚本初始化 MySQL 数据库, 修改 Nacos 配置文件, 配置数据库的连接

1
2
3
4
5
6
7
8
9
10
11
12
# 数据库连接信息配置
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://ip:port/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos

启动 Nacos 服务, 验证 8848 端口是否被 java 程序监听, 即可判断是否启动成功

1
2
3
cd /home/nacos-2.0.3/bi
./startup.sh -m standalone
netstat -tulnp | grep 8848

如果需要集群话部署, 可以参考基于Docker的单节点部署集群模式Nacos服务

0x01-1 通过容器部署后端服务——以OA服务为例

首先是完成 Docker 的安装, 可以使用运维脚本InstallDocker.sh安装Docker及相关组件

Docker数据目录的迁移

之前初始化了数据盘, 现在需要将 Docker 等存储空间占用较大的程序或日志放置在数据盘

找到 Docker 的配置文件daemon.json, 修改data-root的配置

1
2
3
4
5
vim /etc/docker/daemon.json
{
"data-root": "/data/docker"
}
chmod -R 755 /data/docker

配置镜像源的授权

找到 Docker 的授权文件config.json, 修改auths选项

1
2
3
4
5
6
7
{
"auths": {
"harbor.domain.com": {
"auth": "YourAuthToken"
}
}
}

配置完成就重启守护进程和 Docker 服务

1
2
systemctl daemon-reload
systemctl restart docker

docker-compose的部署

根据系统架构在 Github 拉取最新的Release

1
2
3
cd /usr/local/bin
wget https://github.com/docker/compose/releases/download/v2.32.0/docker-compose-linux-x86_64
mv docker-compose-linux-x86_64 docker-compose && chmod +x docker-compose

在构建docker-compose应用时, 记得也将日志目录映射到数据盘/data目录中

编译并部署OA服务

Jenkins的构建脚本配置

首先使用 Maven 工具将源码编译成一个 Jar 包, 然后基于 JDK8 的基础镜像构建后端服务, Jenkins构建脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
set -exu
cat << EOF > Dockerfile
FROM alpine_jdk8u301:v1
LABEL org.opencontainers.image.authors="songdaoyuan@focus-in.cn"
ENV TZ=Asia/Shanghai
ENV LANG=zh_CN.UTF-8
WORKDIR /project
COPY $target $JOB_NAME.jar
CMD java -server -Xms1g -Xmx2g -Dserver.port=8080 -jar $JOB_NAME.jar --spring.profiles.active=prod
EXPOSE 8080
EOF

# 推送到 harbor 仓库
# 同时推送版本号标签和latest标签
docker build -t harbor.domain.com/hykg/$JOB_NAME:v$BUILD_NUMBER .
docker tag harbor.domain.com/hykg/$JOB_NAME:v$BUILD_NUMBER harbor.domain.com/hykg/$JOB_NAME:latest
docker push harbor.domain.com/hykg/$JOB_NAME:v$BUILD_NUMBER
docker push harbor.domain.com/hykg/$JOB_NAME:latest
docker rmi harbor.domain.com/hykg/$JOB_NAME:v$BUILD_NUMBER
docker rmi harbor.domain.com/hykg/$JOB_NAME:latest

# $ECLOUD_HYKG_APP_HOST实际指向移动云的NAT-弹性IP, 通过端口可以免密连接到不同的服务器
ssh -p 10022 root@$ECLOUD_HYKG_APP_HOST "cd /home/docker-compose/$JOB_NAME/ && bash update.sh"

构建完成后镜像被推送到 harbor 仓库, 最后一次构建完成的镜像会被打上版本标签 v$BUILD_NUMBERlatest

docker-compose.yml的和update.sh的配置

这两个文件和Jenkins构建脚本一起实现了全自动构建与更新的流程

docker-compose.yml的内容

在服务器的/home/docker-compose/$JOB_NAME/路径存放着docker-compose.yml文件和控制镜像更新的update.sh脚本, 这里以OA中的prod-basic-gateway服务为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "2"
services:
prod-basic-gateway:
image: harbor.domain.com/hykg/prod-basic-gateway:latest
container_name: prod-basic-gateway
restart: always
ports:
- "8081:8080"
volumes:
- "/data/logs/focusin-log/prod:/var/logs/focusin/"
networks:
- docker-network
networks:
docker-network:
external: true

可以看到容器使用了预先创建好的一个名为 docker-network 的桥接网络, 组建了容器间的大内网环境, 日志则持久化到数据盘中

这里可以考虑兼容运维习惯, 将/data/logs/软链接到/var/log目录

update.sh的内容

1
2
3
4
5
6
#!/bin/bash
# 更新并且重启容器
echo "正在更新 192.168.0.11:8081 网关服务 镜像"
docker pull harbor.domain.com/hykg/prod-basic-gateway:latest
docker-compose down && docker-compose up -d
docker system prune -f

运行脚本后会首先拉取标签为latest的镜像, 可以保证每次都能获取到最新版本的镜像, 然后重启并且应用新的镜像, 最后使用docker system prune -f可以删除旧的镜像

0x01-2 配置日志映射服务

部署Promtail+Loki+Grafana (PLG架构)的日志服务

这个是推荐的方案, 可以参考之前的文章部署Promtail+Loki+Grafana (PLG架构)的日志服务

快速搭建一个HTTP服务器来应急使用

在特定路径启用一个基于 python 的 http 服务器, 监听 9988 端口, 然后在 NG 上配置反向代理, 通过公网 IP 加端口即可看到日志

1
2
cd /data/logs/focusin-log/prod
python3 -m http.server 9988

0x02 ECS-Zookeeper-0x集群部署示例

ECS-Zookeeper-0x这三台ECS为例, 介绍下基于 Docker 的集群部署

参考文档ZK集群部署

0x02-0 配置节点的hosts解析

编辑/etc/hosts文件, 将 IP 和节点简称写入文件, 便于后续的节点发现和通信, 下面的命令在三台主机上都需要执行

1
2
3
4
5
6
7
8
9
10
11
12
13
vim /etc/hosts
#zk cluster
192.168.0.18 ecs-zookeeper-01
192.168.0.10 ecs-zookeeper-02
192.168.0.16 ecs-zookeeper-03
#zk cluster tiny
192.168.0.18 ecs-zk-01
192.168.0.10 ecs-zk-02
192.168.0.16 ecs-zk-03
#zk cluster ultra tiny
192.168.0.18 zk-01
192.168.0.10 zk-02
192.168.0.16 zk-03

0x02-1 配置节点之间的SSH免密登录

三台主机之间需要配置SSH免密登录, 便于交换文件和信息, 下面的命令在三台主机上都需要执行

1
2
3
4
ssh-keygen -t rsa
ssh-copy-id zk-01
ssh-copy-id zk-02
ssh-copy-id zk-03

0x02-2 使用容器部署Zookeeper服务

安装配置Docker

参考上面的 Docker 部署流程, 部署完成后拉取镜像备用

1
docker pull harbor.domain.com/hykg/zookeeper:3.5.10

Zookeeper配置文件的持久化挂载

主机上/home/建立挂载目录和 zookeeper 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mkdir -p /home/zookeeper-3.5.10/conf
mkdir -p /home/zookeeper-3.5.10/data
cd /home/zookeeper-3.5.10/conf

vim zoo.cfg
# 添加以下内容
clientPort=2181
dataDir=/data
dataLogDir=/data/log
tickTime=2000
initLimit=5
syncLimit=2
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
maxClientCnxns=60
server.1=192.168.0.18:2888:3888
server.2=192.168.0.10:2888:3888
server.3=192.168.0.16:2888:3888
# 为每个机器都分配对应的id, 这里是以机器 1 为例
cd ../data/
touch myid && echo 1 > myid

容器的启动和停止脚本

启动脚本RunZooKeeperCluster.sh

1
2
3
4
vim RunZooKeeperCluster.sh

#!/bin/bash
docker run --network host -v /home/zookeeper-3.5.10/data:/data -v /home/zookeeper-3.5.10/conf:/conf --name zookeeper -d harbor.domain.com/hykg/zookeeper:3.5.10

!!!注意
这里必须使用 host 网络模式, 很重要!!!
不清楚就查阅 docker 中网络模式的区别

停止脚本StopZooKeeperCluster.sh

1
2
3
4
vim StopZooKeeperCluster.sh

#!/bin/bash
docker stop zookeeper

在三台 ECS 上依次启动容器, 等待一段时间后自动组成 Zookeeper 集群

0x03 ECS-Nginx-Master/Slave/Web的部署

ECS-Nginx-Master这台 ECS 为例, 介绍下 NGINX 的部署流程

0x03-0 编译所需包的准备

由于需要一些额外的功能, 这里选择了通过编译安装NGINX 1.20.2

体检将ngx_http_proxy_connect_module-0.0.7模块和NGINX源码解压

1
2
3
4
root@ecs-nginx-master:/home/source# tar -xzf nginx-1.20.2.tar.gz ngx_http_proxy_connect_module-0.0.7.tar.gz
# ···
root@ecs-nginx-master:/home/source# ls
nginx-1.20.2 nginx-1.20.2.tar.gz ngx_http_proxy_connect_module-0.0.7 ngx_http_proxy_connect_module-0.0.7.tar.gz

0x03-1 修补并编译NGINX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@ecs-nginx-master:/home/source# cd nginx-1.20.2/
# 打补丁
root@ecs-nginx-master:/home/source/nginx-1.20.2# patch -p1 < /home/source/ngx_http_proxy_connect_module-0.0.7/patch/proxy_connect_rewrite_1018.patch
patching file src/http/ngx_http_core_module.c
patching file src/http/ngx_http_parse.c
patching file src/http/ngx_http_request.c
patching file src/http/ngx_http_request.h
patching file src/http/ngx_http_variables.c

# build-essential:这是编译软件包所必需的一组工具,包括gcc、g++、make等。
# libpcre3 和 libpcre3-dev:PCRE库支持正则表达式,Nginx的HTTP模块需要它来解析正则表达式。
# zlib1g 和 zlib1g-dev:zlib库用于对HTTP包的内容做gzip格式的压缩。
# libssl-dev:OpenSSL库,如果你需要支持HTTPS,那么就需要这个库。
# libgd-dev:GD库,虽然不是所有情况下都需要,但某些模块可能需要它。
apt update && apt upgrade
apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev libgd-dev -y

# HTTP 模块
# --with-http_ssl_module:启用SSL模块,用于支持HTTPS。
# --with-http_stub_status_module:启用状态统计模块,提供Nginx的状态信息。
# --with-http_gzip_static_module:启用对静态文件的Gzip压缩支持。
# --with-http_realip_module:启用Real IP模块,用于获取真实的客户端IP地址。
# Stream 模块
# --with-stream:启用TCP和UDP代理模块
./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --with-http_gzip_static_module --with-http_realip_module --with-stream --add-module=/home/source/ngx_http_proxy_connect_module-0.0.7
#编译和安装Nginx
make && make install

兼容运维习惯, 软连接文件夹到/etc/nginx, 软连接可执行文件到/usr/local/bin

1
2
cd /etc && ln -s /usr/local/nginx/ ./nginx
cd /usr/local/bin && ln -s /usr/local/nginx/sbin/nginx ./nginx

0x03-2 编辑NGINX配置文件

首先创建日志文件夹, 然后修改 NGINX 配置文件

1
2
mkdir -p /var/log/nginx
vim nginx.conf

下面是主配置文件nginx.conf的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
user root;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /usr/local/nginx/conf/mime.types;

default_type text/html;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 300s;
client_body_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
client_body_buffer_size 1024k;
gzip on;
gzip_min_length 10k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
gzip_vary on;
server_names_hash_max_size 1024;
server_names_hash_bucket_size 512;
client_max_body_size 1024m;

# 引入 项目的配置文件 和 工具网站
include /etc/nginx/sites-enabled/*/*/*.conf;
include /etc/nginx/tools/*.conf;

}

stream {
# 引入 TCP/UDP 的四层代理
include /etc/nginx/stream-enabled/*/*.conf;
}

由配置文件可知

  • 后端的配置文件放置在:

    /etc/nginx/sites-enabled/prod/server/server.conf

  • 前端的配置文件放置在:

    /etc/nginx/sites-enabled/prod/proxy-web/proxy-web.conf

  • 中间件的配置文件放置在:

    /etc/nginx/tools/nacos.conf

  • TCP/UDP的反代配置文件在:

    /etc/nginx/stream-enabled/prod/mysql.conf

0x03-3 使用Systemd管理NGINX服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo "[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target
Wants=network.target
[Service]
# 解除 65535 线程的限制, 不需要可以注释掉
# LimitNOFILE=65535
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s stop
[Install]
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nginx.service

如果在之前的编译过程中修改了路径, 或者修改了 NGINX 的配置文件, 这里创建Systemd服务的时候需要同步修改路径

启用并且验证服务

1
2
3
systemctl daemon-reload
systemctl enable nginx
systemctl start nginx

偶发的Systemd错误处理

偶发情况下, 使用Systemd管理 NGINX 会出现报错

1
nginx.service: Can't open PID file /run/nginx.pid (yet?) after start-post: Operation not permitted

实际上 pid 文件存在, 但是报错信息显示无法找到该文件, 在Systemd的配置文件中加上延迟启动参数可以解决这个问题

0x03-4 前端应用与NGINX WEB

前端应用的编译与发布

上面主要介绍的是 NGINX Master 的配置文件, 对于前端应用来说, 流量首先需要通过 NGINX Master 转发到 NGINX Web, 最后才是转发给前端应用

在 Jenkins 构建流水线中, 前端服务编译完成后, 构建脚本将 dist 包分发到 ECS-Nginx-Web 服务器上, 完整脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash

set -exu

# 控制 node 的内存分配, 解决编译 OOM 的问题
export NODE_OPTIONS="--max-old-space-size=8192"

#前端环境编译

npm install yarn -g

yarn install

node --max-old-space-size=8192 $(which yarn) run build:$env

# $ECLOUD_HYKG_APP_HOST 实际指向的是移动云的NAT网关, 通过不同端口即可到达不同的服务器, 10025 是ECS-NG-WEB
scp -r -P 10025 ./dist/* $ECLOUD_HYKG_APP_HOST:/data/$env/$JOB_NAME

NGINX的双层代理

ECS-Nginx-Web的一层代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 81;
root /data/prod/prod-fs-oa-web-fd/;
location / {
index index.html;
try_files $uri $uri/ /index.html;
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
ECS-Nginx-Master的二层代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
upstream prod-fs-oa-web-fd.com {
server 192.168.0.2:81;
}
server {
listen 18081;
server_name 192.168.0.13;
access_log /var/log/nginx/prod-fs-oa-web-fd.access.log main;
client_max_body_size 0;

location / {
proxy_pass http://prod-fs-oa-web-fd.com/;
proxy_redirect off;
proxy_cookie_path / /;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

0x04 移动云网络架构说明

移动云的网络架构