1 | wget https://nodejs.org/download/release/v14.21.3/node-v14.21.3-linux-x64.tar.gz |
注意nodejs版本不能过高,试过node18构建会报错,建议版本node14~node16
配置环境变量
1 | tee -a /etc/profile << EOF |
使环境变量生效1
source /etc/profile
安装yarn1
npm install -g yarn
https://mirrors.aliyun.com/golang/可以找需要的版本1
2wget https://mirrors.aliyun.com/golang/go1.20.3.linux-amd64.tar.gz
tar xzvf go1.20.3.linux-amd64.tar.gz -C /opt/
1 | tee -a /etc/profile << EOF |
使环境变量生效1
source /etc/profile
1 | wget https://github.com/apache/apisix-dashboard/archive/refs/tags/v2.15.1.tar.gz |
1 | tar xzvf v2.15.1.tar.gz |
1 | make build |
构建完成会打印以下内容,估计要7分钟左右1
2
3
4
5
6
7其他省略...
The bundle size is significantly larger than recommended.
Consider reducing it with code splitting: https://umijs.org/docs/load-on-demand
You can also analyze the project dependencies using ANALYZE=1
Done in 433.32s.
编译完将output拷贝到/opt/apisix-dashboard1
mkdir -p mkdir -p /usr/local/apisix/dashboard && cp -r ./output/* /usr/local/apisix/dashboard
如果是http访问etcd仅需配置ectd的访问地址即可
如果是https访问etcd则需要相关证书
将根证书,etcd-server-key证书,etcd-server证书拷贝到apisix目录下
1 | mkdir -p /usr/local/apisix/ssl |
修改vi /usr/local/apisix/dashboard/conf/conf.yaml
1 | # 其余省略... |
frontend方式运行apisix-dashboard(不推荐)1
2cd /usr/local/apisix/dashboard
./manager-api
安装为系统服务(推荐)1
2
3
4
5
6
7
8
9
10
11tee /usr/lib/systemd/system/apisix-dashboard.service << EOF
[Unit]
Description=apisix-dashboard
Conflicts=apisix-dashboard.service
After=network-online.target
[Service]
WorkingDirectory=/usr/local/apisix/dashboard
ExecStart=/usr/local/apisix/dashboard/manager-api -c /usr/local/apisix/dashboard/conf/conf.yaml
EOF
服务管理1
2
3
4
5
6
7
8
9
10
11
12
13
14 reload下
systemctl daemon-reload
enable apisix-dashboard
systemctl enable apisix-dashboard
start apisix-dashboard
systemctl start apisix-dashboard
stop apisix-dashboard
systemctl stop apisix-dashboard
check apisix-dashboard status
systemctl status apisix-dashboard
若出现以下错误,需要将您的机器IP加入白名单,修改conf.yaml将IP地址或IP网段加入到allow_list内即可1
2
3
4
5
6{
Code: 20002,
Message: "IP address not allowed",
Data: null,
SourceSrv: ""
}
修改vi /usr/local/apisix/dashboard/conf/conf.yaml,加入允许访问的网段即可1
2
3
4
5
6
7# 其余省略...
allow_list: # If we don't set any IP list, then any IP access is allowed by default.
- 127.0.0.1 # The rules are checked in sequence until the first match is found.
- 10.1.80.0/24 # The rules are checked in sequence until the first match is found.
- ::1 # In this example, access is allowed only for IPv4 network 127.0.0.1, and for IPv6 network ::1.
# It also support CIDR like 192.168.1.0/24 and 2001:0db8::/32
# 其余省略...
您必须将这些库 perl 5.6.1+, libpcre, libssl安装在您的电脑之中。 对于 Linux来说, 您需要确认使用 ldconfig 命令,让其在您的系统环境路径中能找到它们。
以下安装的内容就是取自https://raw.githubusercontent.com/apache/apisix/master/utils/linux-install-luarocks.sh(master可以替换为具体版本号)1
2sudo apt-get install make gcc g++ build-essential curl unzip
sudo apt-get install -y libssl-dev perl zlib1g-dev libpcre3 libpcre3-dev libldap2-dev libpq-dev
下载,解压openresty-$VERSION.tar.gz1
2
3
4mkdir -p /opt/src && cd /opt/src
curl -O https://openresty.org/download/openresty-1.21.4.1.tar.gz
tar xzvf openresty-1.21.4.1.tar.gz -C /opt/src
cd /opt/src/openresty-1.21.4.1
1 | ./configure --prefix=/usr/local/openresty \ |
1 | make -j`nproc` && make install |
openresty-openssl111-dev openresty-pcre-dev openresty-zlib-dev
我们应该通过添加 GPG 公钥来安装一些需要的组件。这些可以在之后删除
1 | sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates |
然后导入openresty的 GPG 密钥1
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
接着添加 openresty 的官方 APT 库
1 | echo "deb https://openresty.org/package/ubuntu $(lsb_release -sc) main" > openresty.list |
请注意,这是针对 x86_64 或 amd64 系统的
对于 Aarch64 或 ARM64 系统,你应该使用这个 URL 来代替
1 | echo "deb https://openresty.org/package/arm64/ubuntu $(lsb_release -sc) main" |
现在更新 APT 索引1
sudo apt-get update
安装 Openrety 相关组件1
sudo apt-get -y install openresty-openssl111-dev openresty-pcre-dev openresty-zlib-dev
安装完会生成/usr/local/openresty目录,且/usr/local/openresty/openssl111,/usr/local/openresty/pcre,/usr/local/openresty/zlib这子目录会在该目录下。
1 | wget https://luarocks.org/releases/luarocks-3.8.0.tar.gz |
结果如下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
26root@s91:/opt/src/apisix-3.2.0/utils/luarocks-3.8.0# ./configure --prefix=/usr/local/openresty/luajit \
--with-lua=/usr/local/openresty/luajit/ \
--lua-suffix=jit \
--with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1
--lua-suffix is no longer necessary.
The suffix is automatically detected.
Configuring LuaRocks version 3.8.0...
Lua version detected: 5.1
Lua interpreter found: /usr/local/openresty/luajit/bin/luajit
lua.h found: /usr/local/openresty/luajit/include/luajit-2.1/lua.h
unzip found in PATH: /usr/bin
Done configuring.
LuaRocks will be installed at......: /usr/local/openresty/luajit
LuaRocks will install rocks at.....: /usr/local/openresty/luajit
LuaRocks configuration directory...: /usr/local/openresty/luajit/etc/luarocks
Using Lua from.....................: /usr/local/openresty/luajit
Lua include directory..............: /usr/local/openresty/luajit/include/luajit-2.1
* Type make and make install:
to install to /usr/local/openresty/luajit as usual.
* Type make bootstrap:
to install LuaRocks into /usr/local/openresty/luajit as a rock.
设置环境变量1
vi /etc/profile
/etc/profile文件最后添加如下代码1
2export OPENRESTY_HOME=/usr/local/openresty
export PATH=$PATH:$OPENRESTY_HOME/bin:$OPENRESTY_HOME/luajit/bin
使得环境变量生效1
source /etc/profile
主要是为了加一些apisix所需的模块
To enable etcd client certificate you need to build APISIX-Base, seehttps://apisix.apache.org/docs/apisix/FAQ#how-do-i-build-the-apisix-base-environment
apisix提供的openresty构建脚本
我将版本改为2.5.13,即version=${version:-0.0.0}改为了version=2.5.13。
由于git clone经常出问题(github不稳定,翻墙会好一些),我改了下脚本,可以先将需要的包都下载下来(所需的源码包百度网盘下载)[https://pan.baidu.com/s/1X8U4_pIL86QH3wJuhrv6LQ?pwd=k8sl]
可以提前将下载好的安装包放到构建脚本同目录下,构建脚本内容如下,我处理了下:1.有些解压出来不带v,但是文件名带v 2.解压出来是大写的。这两种情况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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 !/usr/bin/env bash
set -euo pipefail
set -x
version=${version:-2.15.3}
if ([ $# -gt 0 ] && [ "$1" == "latest" ]) || [ "$version" == "latest" ]; then
ngx_multi_upstream_module_ver="master"
mod_dubbo_ver="master"
apisix_nginx_module_ver="main"
wasm_nginx_module_ver="main"
lua_var_nginx_module_ver="master"
grpc_client_nginx_module_ver="main"
amesh_ver="main"
debug_args="--with-debug"
OR_PREFIX=${OR_PREFIX:="/usr/local/openresty-debug"}
else
ngx_multi_upstream_module_ver="1.1.1"
mod_dubbo_ver="1.0.2"
apisix_nginx_module_ver="1.12.0"
wasm_nginx_module_ver="0.6.4"
lua_var_nginx_module_ver="v0.5.3"
grpc_client_nginx_module_ver="v0.4.2"
amesh_ver="main"
debug_args=${debug_args:-}
OR_PREFIX=${OR_PREFIX:="/usr/local/openresty"}
fi
prev_workdir="$PWD"
repo=$(basename "$prev_workdir")
workdir=$(mktemp -d)
cd "$workdir" || exit 1
or_ver="1.21.4.1"
wget --no-check-certificate https://openresty.org/download/openresty-${or_ver}.tar.gz
tar -zxvpf openresty-${or_ver}.tar.gz > /dev/null
if [ "$repo" == ngx_multi_upstream_module ]; then
cp -r "$prev_workdir" ./ngx_multi_upstream_module-${ngx_multi_upstream_module_ver}
else
unzip ${prev_workdir}/ngx_multi_upstream_module-${ngx_multi_upstream_module_ver}.zip
fi
if [ "$repo" == mod_dubbo ]; then
cp -r "$prev_workdir" ./mod_dubbo-${mod_dubbo_ver}
else
unzip ${prev_workdir}/mod_dubbo-${mod_dubbo_ver}.zip
fi
if [ "$repo" == apisix-nginx-module ]; then
cp -r "$prev_workdir" ./apisix-nginx-module-${apisix_nginx_module_ver}
else
unzip ${prev_workdir}/apisix-nginx-module-${apisix_nginx_module_ver}.zip
fi
if [ "$repo" == wasm-nginx-module ]; then
cp -r "$prev_workdir" ./wasm-nginx-module-${wasm_nginx_module_ver}
else
unzip ${prev_workdir}/wasm-nginx-module-${wasm_nginx_module_ver}.zip
fi
if [ "$repo" == lua-var-nginx-module ]; then
cp -r "$prev_workdir" ./lua-var-nginx-module-${lua_var_nginx_module_ver}
else
unzip ${prev_workdir}/lua-var-nginx-module-${lua_var_nginx_module_ver#*v}.zip
fi
if [ "$repo" == grpc-client-nginx-module ]; then
cp -r "$prev_workdir" ./grpc-client-nginx-module-${grpc_client_nginx_module_ver}
else
unzip ${prev_workdir}/grpc-client-nginx-module-${grpc_client_nginx_module_ver#*v}.zip
fi
if [ "$repo" == amesh ]; then
cp -r "$prev_workdir" ./amesh-${amesh_ver}
else
unzip ${prev_workdir}/Amesh-${amesh_ver}.zip
fi
cd ngx_multi_upstream_module-${ngx_multi_upstream_module_ver} || exit 1
./patch.sh ../openresty-${or_ver}
cd ..
cd apisix-nginx-module-${apisix_nginx_module_ver}/patch || exit 1
./patch.sh ../../openresty-${or_ver}
cd ../..
cd wasm-nginx-module-${wasm_nginx_module_ver} || exit 1
./install-wasmtime.sh
cd ..
cc_opt=${cc_opt:-}
ld_opt=${ld_opt:-}
luajit_xcflags=${luajit_xcflags:="-DLUAJIT_NUMMODE=2 -DLUAJIT_ENABLE_LUA52COMPAT"}
no_pool_patch=${no_pool_patch:-}
TODO: remove old NGX_HTTP_GRPC_CLI_ENGINE_PATH once we have released a new
version of grpc-client-nginx-module
grpc_engine_path="-DNGX_GRPC_CLI_ENGINE_PATH=$OR_PREFIX/libgrpc_engine.so -DNGX_HTTP_GRPC_CLI_ENGINE_PATH=$OR_PREFIX/libgrpc_engine.so"
cd openresty-${or_ver} || exit 1
FIXME: remove this once 1.21.4.2 is released
rm -rf bundle/LuaJIT-2.1-20220411
lj_ver=2.1-20230119
wget "https://github.com/openresty/luajit2/archive/v$lj_ver.tar.gz" -O "LuaJIT-$lj_ver.tar.gz"
tar -xzf LuaJIT-$lj_ver.tar.gz
mv luajit2-* bundle/LuaJIT-2.1-20220411
${lua_var_nginx_module_ver#*v}是为了去掉v
./configure --prefix="$OR_PREFIX" \
--with-cc-opt="-DAPISIX_BASE_VER=$version $grpc_engine_path $cc_opt" \
--with-ld-opt="-Wl,-rpath,$OR_PREFIX/wasmtime-c-api/lib $ld_opt" \
$debug_args \
--add-module=../mod_dubbo-${mod_dubbo_ver} \
--add-module=../ngx_multi_upstream_module-${ngx_multi_upstream_module_ver} \
--add-module=../apisix-nginx-module-${apisix_nginx_module_ver} \
--add-module=../apisix-nginx-module-${apisix_nginx_module_ver}/src/stream \
--add-module=../apisix-nginx-module-${apisix_nginx_module_ver}/src/meta \
--add-module=../wasm-nginx-module-${wasm_nginx_module_ver} \
--add-module=../lua-var-nginx-module-${lua_var_nginx_module_ver#*v} \
--add-module=../grpc-client-nginx-module-${grpc_client_nginx_module_ver#*v} \
--with-poll_module \
--with-pcre-jit \
--without-http_rds_json_module \
--without-http_rds_csv_module \
--without-lua_rds_parser \
--with-stream \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-http_v2_module \
--without-mail_pop3_module \
--without-mail_imap_module \
--without-mail_smtp_module \
--with-http_stub_status_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_secure_link_module \
--with-http_random_index_module \
--with-http_gzip_static_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-threads \
--with-compat \
--with-luajit-xcflags="$luajit_xcflags" \
$no_pool_patch \
-j`nproc`
make -j`nproc`
sudo make install
cd ..
cd apisix-nginx-module-${apisix_nginx_module_ver} || exit 1
sudo OPENRESTY_PREFIX="$OR_PREFIX" make install
cd ..
cd wasm-nginx-module-${wasm_nginx_module_ver} || exit 1
sudo OPENRESTY_PREFIX="$OR_PREFIX" make install
cd ..
cd grpc-client-nginx-module-${grpc_client_nginx_module_ver#*v} || exit 1
sudo sed -i s#https://go.dev/dl/#https://golang.google.cn/dl/#g install-util.sh
sudo /usr/local/go/bin/go env -w GO111MODULE=on
sudo /usr/local/go/bin/go env -w GOPROXY=https://goproxy.cn,direct
sudo OPENRESTY_PREFIX="$OR_PREFIX" make install
cd ..
cd Amesh-${amesh_ver} || exit 1
sudo OPENRESTY_PREFIX="$OR_PREFIX" sh -c 'PATH="${PATH}:/usr/local/go/bin" make install'
cd ..
package etcdctl
ETCD_ARCH="amd64"
ETCD_VERSION=${ETCD_VERSION:-'3.5.4'}
ARCH=${ARCH:-$(uname -m | tr '[:upper:]' '[:lower:]')}
if [[ $ARCH == "arm64" ]] || [[ $ARCH == "aarch64" ]]; then
ETCD_ARCH="arm64"
fi
wget -q https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-${ETCD_ARCH}.tar.gz
tar xf etcd-v${ETCD_VERSION}-linux-${ETCD_ARCH}.tar.gz
ship etcdctl under the same bin dir of openresty so we can package it easily
sudo cp etcd-v${ETCD_VERSION}-linux-${ETCD_ARCH}/etcdctl "$OR_PREFIX"/bin/
rm -rf etcd-v${ETCD_VERSION}-linux-${ETCD_ARCH}
设置环境变量1
vi /etc/profile
添加以下内容1
2
3
4cat >> /etc/profile << EOF
export $OPENRESTY_HOME=/usr/local/openresty
export PATH=$PATH:$OPENRESTY_HOME/bin
EOF
然后执行1
souce /etc/profile
进入APISIX源码目录
设置APISIX版本,并创建目录
1 | APISIX_VERSION=2.15.3 |
下载源码包1
curl -O https://downloads.apache.org/apisix/${APISIX_VERSION}/apache-apisix-${APISIX_VERSION}-src.tgz
解压源码包1
mkdir -p /opt/src/apisix-${APISIX_VERSION} && tar xzvf apache-apisix-${APISIX_VERSION}-src.tgz -C /opt/src/apisix-${APISIX_VERSION}
修改luarocks源,添加以下配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16mkdir -p ~/.luarocks
cat >> ~/.luarocks/config-5.1.lua << EOF
rocks_servers = {
{
"https://luarocks.cn",
"https://luarocks.org",
"https://raw.githubusercontent.com/rocks-moonscript-org/moonrocks-mirror/master/",
"https://luafr.org/luarocks/",
"http://luarocks.logiceditor.com/rocks"
}
}
variables = {
OPENSSL_INCDIR = "/usr/local/openresty/openssl111/include",
OPENSSL_LIBDIR = "/usr/local/openresty/openssl111/lib"
}
EOF
构建1
2
3
4 Switch to the apisix-${APISIX_VERSION} directory
cd apisix-${APISIX_VERSION}
Create dependencies
make deps
或指定源地址1
make deps ENV_LUAROCKS_SERVER=https://luarocks.cn
构建的话由于网络问题,可能需要多试几次
最后安装1
2 Install apisix command
make install
1 | sudo tee -a /etc/security/limits.conf << EOF |
设置luarocks1
2
3OPENSSL_PREFIX=/usr/local/openresty/openssl111
luarocks config variables.OPENSSL_LIBDIR ${OPENSSL_PREFIX}/lib
luarocks config variables.OPENSSL_INCDIR ${OPENSSL_PREFIX}/include
拷贝源码构建目录下的apisix目录以及deps目录到 /usr/local/apisix1
cp -r /opt/src/apisix-${APISIX_VERSION}/apisix/ /opt/src/apisix-${APISIX_VERSION}/deps/ /usr/local/apisix/
测试apisix1
2/usr/bin/apisix test
/usr/bin/apisix init
分别在三个节点上起etcd服务1
2
3
4
5
6
7
8
9
10
11
12
13/opt/etcd3/etcd \
--name s1 \
--data-dir /tmp/etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://0.0.0.0:2380 \
--initial-cluster s1=http://0.0.0.0:2380 \
--initial-cluster-token tkn \
--initial-cluster-state new \
--log-level info \
--logger zap \
--log-outputs stderr
1 | /opt/etcd3/etcd \ |
1 | /opt/etcd3/etcd \ |
如果是使用http的话,就配置下host就行了1
2
3
4
5
6
7
8
9# 其他配置省略...
etcd:
host:
- "http://10.1.80.91:2379"
- "http://10.1.80.92:2379"
- "http://10.1.80.93:2379"
# 其他配置省略...
如果是使用https的话,需要配置下etcd证书
创建放etcd证书的目录,并将证书拷贝过去,由于我是将etcd和apisix安装在一台上了,请根据具体情况来操作1
2
3sudo mkdir -p /usr/local/apisix/ssl
sudo cp /opt/etcd3/ssl/{etcd-ca.pem,etcd-server-key.pem,etcd-server.pem} /usr/local/apisix/ssl
sudo chmod a+r /usr/local/apisix/ssl/etcd-server-key.pem
chmod 666 /usr/local/apisix/ssl/etcd-server-key.pem 为什么加写权限,见常见问题
修改APISIX配置文件,可以先备份下,然后用config-default.yaml这个模板1
2
3cp /usr/local/apisix/conf/config.yaml /usr/local/apisix/conf/config.yaml.bak
cp /usr/local/apisix/conf/config-default.yaml /usr/local/apisix/conf/config.yaml
vi /usr/local/apisix/conf/config.yaml
修改内容如下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# 其余省略...
ssl:
enable: true
listen: # APISIX listening port in https.
- port: 9443
enable_http2: true
# - ip: 127.0.0.3 # Specific IP, If not set, the default value is `0.0.0.0`.
# port: 9445
# enable_http2: true
ssl_trusted_certificate: /usr/local/apisix/ssl/etcd-ca.pem # Specifies a file path with trusted CA certificates in the PEM format
# 其余省略...
etcd:
host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
- "https://10.1.80.91:2379" # multiple etcd address, if your etcd cluster enables TLS, please use https scheme,
- "https://10.1.80.92:2379" # multiple etcd address, if your etcd cluster enables TLS, please use https scheme,
- "https://10.1.80.93:2379" # multiple etcd address, if your etcd cluster enables TLS, please use https scheme,
# e.g. https://127.0.0.1:2379.
prefix: /apisix # configuration prefix in etcd
use_grpc: false # enable the experimental configuration sync via gRPC
timeout: 30 # 30 seconds. Use a much higher timeout (like an hour) if the `use_grpc` is true.
#resync_delay: 5 # when sync failed and a rest is needed, resync after the configured seconds plus 50% random jitter
#health_check_timeout: 10 # etcd retry the unhealthy nodes after the configured seconds
startup_retry: 2 # the number of retry to etcd during the startup, default to 2
#user: root # root username for etcd
#password: 5tHkHhYkjr6cQY # root password for etcd
tls:
# To enable etcd client certificate you need to build APISIX-Base, see
# https://apisix.apache.org/docs/apisix/FAQ#how-do-i-build-the-apisix-base-environment
cert: /usr/local/apisix/ssl/etcd-server.pem # path of certificate used by the etcd client
key: /usr/local/apisix/ssl/etcd-server-key.pem # path of key used by the etcd client
verify: true # whether to verify the etcd endpoint certificate when setup a TLS connection to etcd,
启动apisix1
2
3/usr/bin/apisix test
/usr/bin/apisix init
/usr/bin/apisix start
如果以这种方式启动了,当使用systemd服务方式启动时,别忘了关掉之前的apisix进程
为APISIX添加systemd服务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21sudo tee -a /usr/lib/systemd/system/apisix.service << EOF
apisix systemd service
https://github.com/api7/apisix-build-tools/blob/master/usr/lib/systemd/system/apisix.service
[Unit]
Description=apisix
Conflicts=apisix.service
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
Restart=on-failure
WorkingDirectory=/usr/local/apisix
ExecStart=/usr/bin/apisix start
ExecStop=/usr/bin/apisix stop
ExecReload=/usr/bin/apisix reload
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
启用apisix服务1
2systemctl daemon-reload
systemctl enable apisix
1 | Installing https://luarocks.org/luasec-0.9-1.src.rock |
需要安装openresty-openssl111-dev
1 | sudo apt install -y openresty-openssl111-dev |
在进行./configure编译Nginx提示gzip module requires the zlib library
执行sudo apt install zlib1g-dev可解决
1 | sudo apt install zlib1g-dev |
在进行./configure编译Nginx提示ngx_postgres addon was unable to detect version of the libpq library
执行sudo apt-get install libpq-dev可解决
1 | sudo apt-get install libpq-dev |
安装apixsix make deps时报如下错误,说明没有安装libldap2-dev1
2
3
4Error: Failed installing dependency: https://luarocks.cn/lualdap-1.2.6-1.src.rock - Could not find header file for LDAP
No file ldap.h in /usr/local/include
No file ldap.h in /usr/include
No file ldap.h in /include
遇如下错误,只需将源码apisix-$VERSION/apisix目录拷贝到 /usr/local/apisix安装目录下 cp -r /opt/src/apisix-3.2.0/apisix/ /usr/local/apisix/1
2
3
4
5
6root@s91:/usr/local/apisix# apisix init
/usr/local/openresty//luajit/bin/luajit /usr/local/apisix/apisix/cli/apisix.lua init
/usr/local/openresty//luajit/bin/luajit: cannot open /usr/local/apisix/apisix/cli/apisix.lua: No such file or directory
root@s91:/usr/local/apisix# apisix test
/usr/local/openresty//luajit/bin/luajit /usr/local/apisix/apisix/cli/apisix.lua test
/usr/local/openresty//luajit/bin/luajit: cannot open /usr/local/apisix/apisix/cli/apisix.lua: No such file or directory
遇到如下错误,只需要在/etc/hosts里加一条记录 127.0.0.1 s92.idc3.meeleet.com 即可1
sudo: unable to resolve host s92.idc3.meeleet.com: Temporary failure in name resolution
APISIX make deps构建,如果出现以下错误,需要配置下luarcoks国内源优先,因为默认的源luarocks.org比较慢,使用https://luarocks.cn的源,成功率会高很多,但是可能还有偶尔失败,多试几次就可以了1
2
3
4
5
6
7
8
9
10
11
12Installing https://luarocks.org/lua-resty-radixtree-2.8.2-0.src.rock
Missing dependencies for lua-resty-radixtree 2.8.2-0:
lua-resty-expr 1.3.0 (not installed)
lua-resty-radixtree 2.8.2-0 depends on lua-resty-ipmatcher (0.6.1-0 installed)
lua-resty-radixtree 2.8.2-0 depends on lua-resty-expr 1.3.0 (not installed)
Installing https://luarocks.org/lua-resty-expr-1.3.0-0.rockspec
Cloning into 'lua-resty-expr'...
fatal: unable to access 'https://github.com/api7/lua-resty-expr/': GnuTLS recv error (-110): The TLS connection was non-properly terminated.
Error: Failed installing dependency: https://luarocks.org/lua-resty-radixtree-2.8.2-0.src.rock - Failed installing dependency: https://luarocks.org/lua-resty-expr-1.3.0-0.rockspec - Failed cloning git repository.
make: *** [Makefile:152: deps] Error 1
1 | 2023/04/17 04:28:11 [warn] 110028#110028: *44161 [lua] v3.lua:716: request_chunk(): https://10.1.80.93:2379: /usr/local/apisix/ssl/etcd-server-key.pem: Permission denied. Retrying, context: ngx.timer |
需要给证书文件所有用户以读权限1
sudo chmod a+r /usr/local/apisix/ssl/etcd-server-key.pem
不需要,因为这里OpenResty用的LuaJIT来运行
1 | To uninstall the APISIX runtime, run: |
How to Use the OpenResty Web Framework for Nginx on Ubuntu 16.04
]]>IP | 主机名 |
---|---|
10.1.80.91 | s1 |
10.1.80.92 | s2 |
10.1.80.93 | s3 |
cfssl版本 | 可执行文件存放目录 | 证书存放目录 |
---|---|---|
1.6.2 | /usr/local/bin | /tmp/certs |
1 | curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.2/cfssl_1.6.2_linux_amd64 -o /tmp/cfssl |
打印默认证书配置1
2cfssl print-defaults csr
cfssl print-defaults config
需要注意的是,etcd-ca-config.json里面server的配置一定要加上”client auth”,因为APISIX新建集群时候,会默认做一次节点间健康检查,此时并没有像etcdctl那样提供client的证书和私钥,所以节点会用server的证书和私钥来进行通信,此时server证书就互为服务端和客户端了。
并且apisix和etcd集群通信时,etcd由于grpc-gateway的原因,也会把server证书当成客户端证书来验证。
profiles(ca证书不同配置的作用)里面的内容就是CA可以用来发挥作用的功能,一共预置了三种配置,分别对应Server、Peer和Client的证书密钥。
signing即签名证书,key encipherment即加密,server auth即服务器认证,client auth即客户端认证
1 | mkdir -p /tmp/certs |
hosts里写所有etcd节点的ip,所有etcd节点的ip和对应的域名
1 | cat > /tmp/certs/etcd-ca-csr.json <<EOF |
1 | cd /tmp/certs |
会生成以下文件
etcd-ca-key.pem为CA的私钥,请妥善保管
etcd-ca.csr文件为证书请求文件,可以删除
Server用来服务端客户端通信的,Peer用来节点间通信的,它们共用同一套配置
1 | mkdir -p /tmp/certs |
1 | cfssl gencert -ca=etcd-ca.pem -ca-key=etcd-ca-key.pem -config=etcd-ca-config.json -profile=server etcd-csr.json | cfssljson -bare etcd-server |
生成以下文件
客户端证书不需要hosts字段,只需要CN字段设置为client
1 | mkdir -p /tmp/certs |
1 | cfssl gencert -ca=etcd-ca.pem -ca-key=etcd-ca-key.pem -config=etcd-ca-config.json -profile=client etcd-client-csr.json | cfssljson -bare etcd-client |
生成以下文件
s91,s92,s93上安装etcd
1 | ETCD_VER=v3.5.4 |
每个节点创建证书存放目录1
mkdir -p /opt/etcd3/ssl
方便起见,就把所有文件都传了1
2
3scp -r /tmp/certs/etcd-*.pem root@10.1.80.91:/opt/etcd3/ssl
scp -r /tmp/certs/etcd-*.pem root@10.1.80.92:/opt/etcd3/ssl
scp -r /tmp/certs/etcd-*.pem root@10.1.80.93:/opt/etcd3/ssl
–initial-cluster-token etcd-cluster-tkn “etcd-cluster-tkn”可以替换成你自己需要的值
1 | ./etcd -name etcd1 \ |
节点1
1 | make sure etcd process has write access to this directory |
节点2
1 | make sure etcd process has write access to this directory |
节点3
1 | make sure etcd process has write access to this directory |
检查etcd集群状态
1 | ETCDCTL_API=3 |
如果之前通过frontend方式运行过apisix,记得要停掉之前的apisix进程。
证书先拷贝到/opt/etcd3/ssl目录1
2
3
4
5 make sure etcd process has write access to this directory
remove this directory if the cluster is new; keep if restarting etcd
rm -rf /tmp/etcd/s1
to write service file for etcd
1 | cat > /tmp/s1.service <<EOF |
1 | to start service |
1 | cat > /tmp/s2.service <<EOF |
1 | cat > /tmp/s3.service <<EOF |
Check status:
etcdctl检查1
2
3
4
5
6
7ETCDCTL_API=3
/opt/etcd3/etcdctl \
--cacert /opt/etcd3/ssl/etcd-ca.pem \
--cert /opt/etcd3/ssl/etcd-server.pem \
--key /opt/etcd3/ssl/etcd-server-key.pem \
--endpoints 10.1.80.91:2379,10.1.80.92:2379,10.1.80.93:2379 \
endpoint health
curl检查1
2 curl查看健康状态
curl --cacert /opt/etcd3/ssl/etcd-ca.pem --cert /opt/etcd3/ssl/etcd-server.pem --key /opt/etcd3/ssl/etcd-server-key.pem https://10.1.80.91:2379/health
注意切换到指定用户
若结果如下,表示集群状态正常1
2
310.1.80.93:2379 is healthy: successfully committed proposal: took = 6.506639ms
10.1.80.91:2379 is healthy: successfully committed proposal: took = 6.427877ms
10.1.80.92:2379 is healthy: successfully committed proposal: took = 7.161322ms
IP | 主机名 |
---|---|
10.1.80.91 | s1 |
10.1.80.92 | s2 |
10.1.80.93 | s3 |
cfssl版本 | 可执行文件存放目录 | 证书存放目录 |
---|---|---|
1.6.2 | /usr/local/bin | /tmp/certs |
1 | curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.2/cfssl_1.6.2_linux_amd64 -o /tmp/cfssl |
1 | mkdir -p /tmp/certs |
结果1
2
3
4
5
6
7
8
9
10
11
12
13
14 CSR configuration
/tmp/certs/root-ca-csr.json
CSR
/tmp/certs/root-ca.csr
self-signed root CA public key
/tmp/certs/root-ca.pem
self-signed root CA private key
/tmp/certs/root-ca-key.pem
cert-generation configuration for other TLS assets
/tmp/certs/gencert.json
由于3个节点用同一套证书,所以只需生成一套即可
1 | mkdir -p /tmp/certs |
结果1
2
3
4
5
6
7
8
9-rw-r--r-- 1 root root 323 Sep 30 05:28 etcd-ca-csr.json
-rw-r--r-- 1 root root 1098 Sep 30 05:28 etcd.csr
-rw------- 1 root root 1679 Sep 30 05:28 etcd-key.pem
-rw-r--r-- 1 root root 1493 Sep 30 05:28 etcd.pem
-rw-r--r-- 1 root root 205 Sep 30 05:25 gencert.json
-rw-r--r-- 1 root root 1017 Sep 30 05:25 root-ca.csr
-rw-r--r-- 1 root root 221 Sep 30 05:25 root-ca-csr.json
-rw------- 1 root root 1679 Sep 30 05:25 root-ca-key.pem
-rw-r--r-- 1 root root 1346 Sep 30 05:25 root-ca.pem
将证书传到s91, s92, s93,方便起见,就把所有文件都传了1
2
3scp -r /tmp/certs/ root@10.1.80.91:/root/
scp -r /tmp/certs/ root@10.1.80.92:/root/
scp -r /tmp/certs/ root@10.1.80.93:/root/
这里请更改成您自己所用的系统用户名
s91,s92,s93上安装etcd
1 | ETCD_VER=v3.4.21 |
分别在s91,s92,s93上运行etcd
1 | make sure etcd process has write access to this directory |
1 | /opt/etcd3/etcd --name s2 \ |
1 | /opt/etcd3/etcd --name s3 \ |
–initial-cluster-token tkn “tkn”可以替换成你自己需要的值
检查etcd集群状态
1 | ETCDCTL_API=3 /opt/etcd3/etcdctl \ |
1 | after transferring certs to remote machines |
1 | to start service |
1 | after transferring certs to remote machines |
1 | after transferring certs to remote machines |
Check status:
1 | ETCDCTL_API=3 /opt/etcd3/etcdctl \ |
注意切换到指定用户
若结果如下,表示集群状态正常1
2
310.1.80.92:2379 is healthy: successfully committed proposal: took = 9.184858ms
10.1.80.93:2379 is healthy: successfully committed proposal: took = 8.681243ms
10.1.80.91:2379 is healthy: successfully committed proposal: took = 11.855811ms
假设某公司有多个数据中心,数据中心1以内部使用idc1.meeleet.com,数据中心2使用idc2.meeleet.com,数据中心3使用idc3.meeleet.com。我们这里以数据中心3内部需要搭建私网DNS服务器为例来讲,idc3.meeleet.com(具体实施过程中,按读者实际情况进行修改)
私网IP地址 | 简称 | 是否为主DNS服务器 |
---|---|---|
10.1.80.220 | ns1 | 是(主DNS服务器) |
10.1.80.221 | ns2 | 否(备DNS服务器) |
私网IP地址 | 简称 | |
---|---|---|
10.1.80.91 | s91 | |
10.1.80.92 | s92 |
在主DNS服务器及备DNS服务器上安装BIND
ON ns1 ns2
1 | sudo apt install bind9 bind9utils bind9-doc |
修改启动参数1
sudo vi /etc/default/named
OPTIONS参数最后添加-4,使用Ipv4模式1
2
3
4
5
6
run resolvconf?
RESOLVCONF=no
startup options for the server
OPTIONS="-u bind -4"
重启bind
1 | sudo systemctl restart bind9 |
on ns1
打开named.conf.options并修改
1 | sudo vi /etc/bind/named.conf.options |
1 | acl "trusted" { |
注意forwards配置块,有三个ip地址,其中223.5.5.5,223.6.6.6是阿里云公共DNS,114.114.114.114是电信提供公共的DNS。forwards可以为无法直接连接到外网的机器提供域名解析服务。
完成之后,保存并关闭named.conf.options文件。上面的配置指定只有您自己的服务器(受信任”trusted”的服务器)能够查询您的DNS服务器的外部域。
on ns1
打开named.conf.local,用vi来编辑
1 | sudo vi /etc/bind/named.conf.local |
正向、反向解析,指定配置文件路径
1 |
|
on ns1
创建转发区域文件存放目录
1 | mkdir -p /etc/bind/zones |
复制db.local并修改
1 | sudo cp /etc/bind/db.local /etc/bind/zones/db.idc3.meeleet.com |
1 | ; |
将localhost替换为idc3.meeleet.com,删除含delete this line标记的行
文件最后添加上DNS服务器NS记录
1 |
|
给这个域内的主机添加A记录
1 | ... |
每次编辑区域文件时,需要在重新启动命名进程之前增加Serial值。在这里,将其增加为3,注意增加Serial值非常重要!非常重要!非常重要!最终zone配置文件内容如下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
; BIND data file for local loopback interface
;
TTL 604800
@ IN SOA idc3.meeleet.com. root.idc3.meeleet.com. (
3 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
; name servers - NS records
IN NS ns1.idc3.meeleet.com.
IN NS ns2.idc3.meeleet.com.
; name servers - A records
ns1.idc3.meeleet.com. IN A 10.1.80.220
ns2.idc3.meeleet.com. IN A 10.1.80.221
; 10.1.80.0/24 - A records
s91.idc3.meeleet.com. IN A 10.1.80.91
s92.idc3.meeleet.com. IN A 10.1.80.92
保存/etc/bind/zones/db.idc3.meeleet.com
1 | sudo cp /etc/bind/db.127 /etc/bind/zones/db.10.1.80 |
1 | TTL 604800 |
删除含delete this line标记的行
最终1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19TTL 604800
@ IN SOA ns1.idc3.meeleet.com. root.idc3.meeleet.com. (
3 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
; name servers - NS records
IN NS ns1.idc3.meeleet.com.
IN NS ns2.idc3.meeleet.com.
; PTR Records
220 IN PTR ns1.idc3.meeleet.com. ; 10.1.80.220
221 IN PTR ns2.idc3.meeleet.com. ; 10.1.80.221
91 IN PTR s91.idc3.meeleet.com. ; 10.1.80.91
92 IN PTR s92.idc3.meeleet.com. ; 10.1.80.92
1 | sudo named-checkzone idc3.meeleet.com /etc/bind/zones/db.idc3.meeleet.com |
1 | sudo named-checkzone 80.1.10.in-addr.arpa /etc/bind/zones/db.10.1.80 |
检查没问题,可以重启bind9
1 | sudo systemctl restart bind9 |
如果有使用ufw,则需要让ufw允许bind9(如果有启用ufw的话)
1 | sudo ufw allow Bind9 |
多数情况下搞一套主备DNS服务器还是很有必要的,这样的话当主备DNS服务器不可用时,备DNS服务器也可以接受DNS查询请求并作出响应。幸运的是,配置一个备DNS服务器比配置主DNS服务器简单多了。
on ns2
编辑named.conf.options文件
1 | sudo vi /etc/bind/named.conf.options |
在文件的顶部,添加包含所有受信任服务器的私有IP地址的ACL。(也支持添加单独的ip,这里我为了方便,直接用网段来表示,这样就不用一个个IP写出来了)
1 | acl "trusted" { |
directory指令下方,添加如下行
1 | . . . |
保存并关闭named.conf.options文件。这个文件应该与ns1的named.conf.options文件相同,只是它应该被配置为监听ns2的私有IP地址。
最终named.conf.options文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24acl "trusted" {
10.1.80.220; # ns1
10.1.80.221; # ns2
10.1.80.0/24; # trusted DNS clients network
};
options {
directory "/var/cache/bind";
recursion yes; # enables recursive queries
allow-recursion { trusted; }; # allows recursive queries from "trusted" clients
allow-query { trusted; }; # allows queries from "trusted" clients
listen-on { 10.1.80.221; }; # ns2 private IP address
allow-transfer { none; }; # disable zone transfers by default
forwarders {
223.5.5.5;
223.6.6.6;
114.114.114.114;
};
dnssec-validation auto;
listen-on-v6 { any; };
};
现在编辑named.conf.loca文件1
sudo vi /etc/bind/named.conf.local
在主DNS服务器上定义与主DNS服务器对应的辅助区域。注意,该类型是slave,文件不包含路径,并且有一个master指令,该指令应该设置为主DNS服务器的私有IP地址。如果您在主DNS服务器中定义了多个反向区域,请确保将它们全部添加到这里:
1 |
|
注意:考虑到它们的负面含义,DigitalOcean倾向于尽可能避免使用“主人”和“奴隶”等术语。在Bind的最新版本中,可以使用primaries而不是masters,并将备服务器的类型定义为secondary服务器而不是slave服务器。然而,从默认的Ubuntu 20.04存储库中安装的BIND版本(如步骤1中所述)将无法识别这些选项,这意味着除非升级,否则您将不得不使用包含较少的术语。
保存并关闭named.conf.local文件
运行以下命令检查配置文件有效性1
sudo named-checkconf
如果命令执行反回没有任何错误,那么就可以重启bind
1 | sudo systemctl restart bind9 |
然后通过修改防火墙规则使得允许DNS客户端的连接到此DNS服务器
1 | sudo ufw allow Bind9 |
到现在为止,DNS主备服务器就搭建好了
从ns1传输到ns2的区域文件放在 ns2的/var/cache/bind/目录
1 | ls /var/cache/bind/ |
在受信任ACL中的所有服务器都可以查询您的DNS服务器之前,必须将每个服务器配置为使用ns1和ns2作为DNS服务器。
假设您的客户端服务器运行Ubuntu,您需要找到与您的专用网络相关联的设备。可以使用ip address命令查询私有子网。在每台客户机上运行以下命令,将突出显示的子网替换为您自己的子网。
1 | ip address show to 10.1.80.0/24 |
本例中私网网卡名为enp0s3。本节中的示例将把enp0s3作为私有网络(10.1.80.0/24网段)网卡,不过您应该根据您的实际情况更改这些示例,以反映您自己的服务器的私有网卡。
修改/etc/netplan/00-installer-config.yaml给s91,s92的相应网卡设置ns1和ns2 DNS服务器的地址,以及search域
1 | vi /etc/netplan/00-installer-config.yaml |
s911
2
3
4
5
6
7
8
9
10
11
12network:
ethernets:
enp0s3:
addresses:
- 10.1.80.91/24
gateway4: 10.1.80.254
nameservers:
addresses: [10.1.80.220,10.1.80.221]
search: [idc3.meeleet.com]
enp0s8:
dhcp4: true
version: 2
s921
2
3
4
5
6
7
8
9
10
11
12
13
14network:
ethernets:
enp0s3:
addresses:
- 10.1.80.92/24
gateway4: 10.1.80.254
nameservers:
addresses:
- 10.1.80.220
- 10.1.80.221
search: [idc3.meeleet.com]
enp0s8:
dhcp4: true
version: 2
DNS服务器列表network->ethernets->enp0s3->nameservers->addresses两种配置方式都可以。search的作用就是就去ping s91时,原来当访问的域名(s91)不能被DNS解析时,resolver会将该域名加上search指定的参数,也就是s91.idc3.meeleet.com,重新请求DNS,直到被正确解析或试完search指定的列表为止。
保存并关闭
使用netplan try尝试新配置,如果有问netplay会在倒计时后自动回滚配置。
现在,检查系统的DNS解析器,以确定您的DNS配置是否已应用:1
sudo systemd-resolve --status
1 | Global |
现在你已拥有了工作在内网的DNS服务器,你需要维护DNS记录以便支持你的服务器环境。
任何时候你要添加新主机到你的服务器环境(在同一个数据中心),添加一个新主机的DNS记录,需要以下步骤:
正向区域文件:为新主机添加A记录,并增加Serial值
反向区域文件:为新主机增加PTR记录,并增加Serial值
添加新的主机私网IP地址到受信任的ACL列表(named.conf.options)
测试配置文件:1
2
3sudo named-checkconf
sudo named-checkzone idc3.meeleet.com /etc/bind/zones/db.idc3.meeleet.com
sudo named-checkzone 80.1.10.in-addr.arpa /etc/bind/zones/db.10.1.80
检查配置文件语法是否正确1
sudo named-checkconf
然后重载bind:1
sudo systemctl reload bind9
如果你要删除某个主机的DNS记录,只需要删除之前添加的该主机的DNS记录信息(就是与之前添加主机DNS记录的步骤相反)
How To Configure BIND as a Private Network DNS Server on Ubuntu 20.04
如何在CentOS-7上将BIND配置为专用网络DNS服务器
]]>VirtualBox虚拟机3台,使用的网络有桥接网卡、网络地址转换(NAT)
桥接网卡网段是10.1.80.0/24,网络地址转换(NAT)是为了能访问internet以便安装软件
节点 | IP | VIP | |
---|---|---|---|
ds1 | 10.1.80.91 | enp0s3 | |
ds2 | 10.1.80.92 | enp0s3 | |
rs1 | 10.1.80.93 | lo:0 | |
rs2 | 10.1.80.95 | lo:0 |
VIP地址为10.1.80.90
RS的RIP可以使用私网或公网IP, 文中DS与RS用的是同一网段。实际场景,一般情况下RS的RIP与VIP是不同网段的。
安装ipvsadm1
sudo yum install -y ipvsadm
安装keepalived1
sudo yum install -y keepalived
备份keepalived.conf配置文件1
sudo cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
1 | sudo vi /etc/keepalived/keepalived.conf |
1 |
|
ds1作为MASTER优先级比ds2高,ds1设置priority 101,ds2设置priority 100
重启keepalived1
sudo systemctl restart keepalived
防火墙开放80端口1
sudo firewall-cmd --zone=public --add-port=80/tcp --permanent
防火墙允许vrrp的组播
1 | sudo firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 --in-interface enp0s3 --destination 224.0.0.18 --protocol vrrp -j ACCEPT |
防火墙重新加载
1 | sudo firewall-cmd --reload |
安装ipvsadm1
sudo yum install -y ipvsadm
安装keepalived1
sudo yum install -y keepalived
备份keepalived.conf配置文件1
sudo cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
1 | sudo vi /etc/keepalived/keepalived.conf |
1 |
|
重启keepalived1
sudo systemctl restart keepalived
防火墙开放80端口1
sudo firewall-cmd --zone=public --add-port=80/tcp --permanent
防火墙允许vrrp的组播
1 | sudo firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 --in-interface enp0s3 --destination 224.0.0.18 --protocol vrrp -j ACCEPT |
防火墙重新加载1
sudo firewall-cmd --reload
1 | sudo mkdir -p /opt/scripts |
1 | sudo vi /opt/scripts/lvs_rs.sh |
脚本如下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 !/bin/bash
VIP=10.1.80.90
case "$1" in
start)
ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP
/sbin/route add -host $VIP dev lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
sysctl -p >/dev/null 2>&1
echo "RealServer Start OK"
;;
stop)
ifconfig lo:0 down
route del $VIP >/dev/null 2>&1
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce
echo "RealServer Stoped"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
exit 0
1 | sudo chmod +x /opt/scripts/lvs_rs.sh |
安装tengine1
2
3
4
5
6
7sudo mkdir -p /tmp/software
sudo cd /tmp/software
sudo curl -O https://tengine.taobao.org/download/tengine-2.3.3.tar.gz
sudo tar -xzvf tengine-2.3.3.tar.gz
sudo cd tengine-2.3.3
sudo yum install -y gcc make pcre-devel openssl-devel
sudo ./configure --prefix=/usr/local/tengine
修改index.html1
sudo vi /usr/local/tengine/html/index.html
1 | 其余省略... |
启动tengine1
sudo /usr/local/tengine/sbin/nginx
防火墙设置开放80端口1
2firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload
1 | sudo mkdir -p /opt/scripts |
1 | sudo vi /opt/scripts/lvs_rs.sh |
脚本如下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 !/bin/bash
VIP=10.1.80.90
case "$1" in
start)
ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP
/sbin/route add -host $VIP dev lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
sysctl -p >/dev/null 2>&1
echo "RealServer Start OK"
;;
stop)
ifconfig lo:0 down
route del $VIP >/dev/null 2>&1
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce
echo "RealServer Stoped"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
exit 0
1 | sudo chmod +x /opt/scripts/lvs_rs.sh |
安装tengine1
2
3
4
5
6
7sudo mkdir -p /tmp/software
sudo cd /tmp/software
sudo curl -O https://tengine.taobao.org/download/tengine-2.3.3.tar.gz
sudo tar -xzvf tengine-2.3.3.tar.gz
sudo cd tengine-2.3.3
sudo yum install -y gcc make pcre-devel openssl-devel
sudo ./configure --prefix=/usr/local/tengine
修改index.html1
sudo vi /usr/local/tengine/html/index.html
1 | 其余省略... |
启动tengine1
sudo /usr/local/tengine/sbin/nginx
防火墙设置开放80端口1
2firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload
1 | sudo tcpdump -vvv -n -i enp0s3 host 224.0.0.18 |
在ds1上执行(ds2,rs1,rs2都可以的)1
2
3
414:43:04.752665 IP (tos 0xc0, ttl 255, id 130, offset 0, flags [none], proto VRRP (112), length 40)
10.1.80.91 > 224.0.0.18: vrrp 10.1.80.91 > 224.0.0.18: VRRPv2, Advertisement, vrid 51, prio 101, authtype simple, intvl 1s, length 20, addrs: 10.1.80.90 auth "1111^@^@^@^@"
14:43:05.753059 IP (tos 0xc0, ttl 255, id 131, offset 0, flags [none], proto VRRP (112), length 40)
10.1.80.91 > 224.0.0.18: vrrp 10.1.80.91 > 224.0.0.18: VRRPv2, Advertisement, vrid 51, prio 101, authtype simple, intvl 1s, length 20, addrs: 10.1.80.90 auth "1111^@^@^@^@"
可以看到10.1.80.91在发组播消息,10.1.80.91证明我是MASTER,vip(10.1.80.90)在ds1的enp0s3上。
然后把ds1的keepalived关了,sudo systemctl stop keepalived
观察报文变化
1 | 14:45:08.488190 IP (tos 0xc0, ttl 255, id 253, offset 0, flags [none], proto VRRP (112), length 40) |
发现已经由10.1.80.91改为10.1.80.92发组播消息,vip(10.1.80.90)飘到了ds2的enp0s3上。
重新启动ds1上的keepalived,再次观察报文
1 | 14:46:59.177236 IP (tos 0xc0, ttl 255, id 8880, offset 0, flags [none], proto VRRP (112), length 40) |
发现由10.1.80.92改为了10.1.80.91发组播消息了,vip(10.1.80.90)飘回了ds1的enp0s3上。
页面展示 Welcome to tengine! I’m rs1
ds1上查看sudo ipvsadm -ln
1 | [jaychang@ds01 ~]# sudo ipvsadm -ln |
ds1上查看/var/log/keepalived.log(如果未设置过,默认是在/var/log/message)
1 | Oct 8 15:50:14 ds01 Keepalived_healthcheckers[15444]: TCP connection to [10.1.80.93]:80 failed. |
从日志内容可以看到10.1.80.93已被剔除了
ds1上查看sudo ipvsadm -ln
1 | [jaychang@ds01 ~]$ sudo ipvsadm -ln |
刷新浏览器页面显示Welcome to tengine! I’m rs2
观察/var/log/keepalived.log(如果未设置过,默认是在/var/log/message)
1 | Oct 8 16:11:41 ds01 Keepalived_healthcheckers[15444]: TCP connection to [10.1.80.93]:80 success. |
ds1上查看sudo ipvsadm -ln
1 | IP Virtual Server version 1.2.1 (size=4096) |
发现10.1.80.93已经重新被加入到服务列表中了
1 | sudo ipvsadm --clear |
1 | sudo ifconfig enp0s3:0 down |
1 | sudo ipvsadm -ln --stats |
1 | sudo ipvsadm -lnc |
1 | sudo tcpdump -vvv -n -i enp0s3 host 224.0.0.18 |
或1
sudo tcpdump -vvv -n -i enp0s3 vrrp
1 | sudo tcpdump -i enp0s3 tcp port 80 and host 10.1.80.62 |
查看详细一点信息
1 | sudo tcpdump -vvv -n -i enp0s3 tcp port 80 and host 10.1.80.62 |
1 | sudo iptables -I INPUT -i enp0s3 -d 224.0.0.18 -p vrrp -j ACCEPT |
开放80端口访问1
sudo firewall-cmd --zone=public --add-port=80/tcp --permanent
关闭80端口访问1
sudo firewall-cmd --zone=public --remove-port=80/tcp --permanent
记得要reload或restart下,防火墙才会生效1
sudo firewall-cmd --reload
或1
sudo systemctl reload firewalld
或1
sudo systemctl restart firewalld
推荐使用第一种方式重新加载防火墙
1 | sudo firewall-cmd --list-port |
1 | sudo echo "/opt/scripts/lvs_rs.sh start" >> /etc/rc.d/rc.local |
在centos7中,/etc/rc.d/rc.local文件的权限被降低了1
sudo chmod +x /etc/rc.d/rc.local
要使得rs1,rs2上的lo:0重启后仍生效的话,要创建一个ifcfg-lo:0文件
1 | sudo cp /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/ifcfg-lo:0 |
1 | sudo vi /etc/sysconfig/network-scripts/ifcfg-lo:0 |
内容如下1
2
3
4
5DEVICE=lo:0
IPADDR=10.1.80.90
NETMASK=255.255.255.255
BROADCAST=10.1.80.90
ONBOOT=yes
重启网络1
sudo service network restart
1 | sudo vi /etc/sysctl.conf |
添加以下配置
1 | net.ipv4.conf.all.arp_ignore = 1 |
刷新文件1
sudo sysctl -p
路由设置添加开启自启动
echo “route add -host 10.1.80.90 dev lo:0” >> /etc/rc.local
LVS High Availability - The Keepalived Solution
]]>对于 Kubernetes 初学者,在搭建K8S集群时,推荐在阿里云或腾讯云采购如下配置:(您也可以使用自己的虚拟机、私有云等您最容易获得的 Linux 环境)
Kubernetes v1.19.x
calico 3.13.1
nginx-ingress 1.5.5
关于二进制安装
kubeadm 是 Kubernetes 官方支持的安装方式,“二进制” 不是。本文档采用 kubernetes.io 官方推荐的 kubeadm 工具安装 kubernetes 集群。
1 | # 在 master 节点和 worker 节点都要执行 |
操作系统兼容性
CentOS版本 | 本文档是否兼容 | 备注 |
---|---|---|
7.8 | 是 | 已验证 |
7.7 | 是 | 已验证 |
7.6 | 是 | 已验证 |
7.5 | 否 | 已证实会出现 kubelet 无法启动的问题 |
7.4 | 否 | 已证实会出现 kubelet 无法启动的问题 |
7.3 | 否 | 已证实会出现 kubelet 无法启动的问题 |
7.2 | 否 | 已证实会出现 kubelet 无法启动的问题 |
修改 hostname
如果您需要修改 hostname,可执行如下指令:
1 | 修改 hostname |
在所有节点执行命令
1 | [root@demo-master-a-1 ~]$ ip route show |
使用 root 身份在所有节点执行如下代码,以安装软件:
请将脚本最后的 1.19.6 替换成您需要的版本号, 脚本中间的 v1.19.x 不要替换
docker hub 镜像请根据自己网络的情况任选一个
1 | 在 master 节点和 worker 节点都要执行 |
手动执行以下代码,结果与快速安装相同。请将脚本第79行(已高亮)的 ${1} 替换成您需要的版本号,例如 1.19.6
docker hub 镜像请根据自己网络的情况任选一个
1 | 在 master 节点和 worker 节点都要执行 |
可以指定安装docker-ce的版本,用yum list docker-ce –showduplicates|sort -r 查看所有版本
1 | !/bin/bash |
WARNING
如果此时执行 systemctl status kubelet 命令,将得到 kubelet 启动失败的错误提示,请忽略此错误,因为必须完成后续步骤中 kubeadm init 的操作,kubelet 才能正常启动
关于初始化时用到的环境变量
请将脚本最后的 1.19.6 替换成您需要的版本号, 脚本中间的 v1.19.x 不要替换
1 | 只在 master 节点执行 |
手动执行以下代码,结果与快速初始化相同。请将脚本第21行(已高亮)的 ${1} 替换成您需要的版本号,例如 1.19.6
1 | 只在 master 节点执行 |
1 | !/bin/bash |
感谢 https://github.com/zhangguanzhang/google_containers (opens new window)提供最新的 google_containers 国内镜像
请确保您的环境符合 安装docker及kubelet 中所有勾选框的要求
请确保您使用 root 用户执行初始化命令
不能下载 kubernetes 的 docker 镜像
安装文档中,默认使用阿里云的 docker 镜像仓库,然而,有时候,该镜像会罢工
如碰到不能下载 docker 镜像的情况,请尝试手工初始化,并修改手工初始化脚本里的第22行(文档中已高亮)为:
1 | imageRepository: gcr.azk8s.cn/google-containers |
检查环境变量,执行如下命令
1 | echo MASTER_IP=${MASTER_IP} && echo APISERVER_NAME=${APISERVER_NAME} && echo POD_SUBNET=${POD_SUBNET} |
kubeadm reset -f
操作ImagePullBackoff / Pending
如果 kubectl get pod -n kube-system -o wide 的输出结果中出现 ImagePullBackoff 或者长时间处于 Pending 的情况,请参考 查看镜像抓取进度
ContainerCreating
如果 kubectl get pod -n kube-system -o wide 的输出结果中某个 Pod 长期处于 ContainerCreating、PodInitializing 或 Init:0/3 的状态,可以尝试:
1 | kubectl describe pod kube-flannel-ds-amd64-8l25c -n kube-system |
如果输出结果中,最后一行显示的是 Pulling image,请耐心等待,或者参考 查看镜像抓取进度
1 | Normal Pulling 44s kubelet, k8s-worker-02 Pulling image "quay.io/coreos/flannel:v0.12.0-amd64" |
1 | kubectl delete pod kube-flannel-ds-amd64-8l25c -n kube-system |
1 | 只在 master 节点执行 |
如果出错请看
ImagePullBackoff / Pending
ContainerCreating
如果kubectl get pod -n kube-system -o wide的输出结果中某个 Pod 长期处于 ContainerCreating、PodInitializing 或 Init:0/3 的状态,可以尝试:
查看该 Pod 的状态,例如:
1 | kubectl describe pod kube-flannel-ds-amd64-8l25c -n kube-system |
如果输出结果中,最后一行显示的是 Pulling image,请耐心等待,或者参考查看镜像抓取进度
1 | Normal Pulling 44s kubelet, k8s-worker-02 Pulling image "quay.io/coreos/flannel:v0.12.0-amd64" |
将该 Pod 删除,系统会自动重建一个新的 Pod,例如:
1 | kubectl delete pod kube-flannel-ds-amd64-8l25c -n kube-system |
在master节点上执行
1 | 只在 master 节点执行 |
可获取kubeadm join 命令及参数,如下所示
1 | kubeadm token create 命令的输出 |
有效时间
该 token 的有效时间为 2 个小时,2小时内,您可以使用此 token 初始化任意数量的 worker 节点。
针对所有的 worker 节点执行
1 | 只在 worker 节点执行 |
在master节点上执行1
2 只在 master 节点执行
kubectl get nodes -o wide
输出结果如下所示:1
2
3
4
5[root@demo-master-a-1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
demo-master-a-1 Ready master 5m3s v1.19.x
demo-worker-a-1 Ready <none> 2m26s v1.19.x
demo-worker-a-2 Ready <none> 3m56s v1.19.x
经常在群里提问为什么 join 不成功的情况大致有这几种:
在worker节点执行以下语句可验证worker节点是否能访问 apiserver
1 | curl -ik https://apiserver.demo:6443 |
如果不能,请在 master 节点上验证
1 | curl -ik https://localhost:6443 |
正常输出结果如下所示:
1 | HTTP/1.1 403 Forbidden |
可能原因
- 如果 master 节点能够访问 apiserver、而 worker 节点不能,则请检查自己的网络设置
- /etc/hosts 是否正确设置?
- 是否有安全组或防火墙的限制?
Kubelet使用的 IP 地址与 master 节点可互通(无需 NAT 映射),且没有防火墙、安全组隔离
如果你使用 vmware 或 virtualbox 创建虚拟机用于 K8S 学习,可以尝试 NAT 模式的网络,而不是桥接模式的网络
WARNING
正常情况下,您无需移除 worker 节点,如果添加到集群出错,您可以移除 worker 节点,再重新尝试添加
在准备移除的 worker 节点上执行
1 | 只在 worker 节点执行 |
在 master 节点 demo-master-a-1 上执行
1 | 只在 master 节点执行 |
如果列表中没有您要移除的节点,则忽略下一个步骤
1 | # 只在 master 节点执行 |
TIP
- 将 demo-worker-x-x 替换为要移除的 worker 节点的名字
- worker 节点的名字可以通过在节点 demo-master-a-1 上执行 kubectl get nodes 命令获得
在master节点上执行1
2 只在 master 节点执行
kubectl apply -f https://kuboard.cn/install-script/v1.19.x/nginx-ingress.yaml
nginx-ingress.yaml内容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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168# 如果打算用于生产环境,请参考 https://github.com/nginxinc/kubernetes-ingress/blob/v1.5.5/docs/installation.md 并根据您自己的情况做进一步定制
apiVersion: v1
kind: Namespace
metadata:
name: nginx-ingress
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress
namespace: nginx-ingress
---
apiVersion: v1
kind: Secret
metadata:
name: default-server-secret
namespace: nginx-ingress
type: Opaque
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN2akNDQWFZQ0NRREFPRjl0THNhWFhEQU5CZ2txaGtpRzl3MEJBUXNGQURBaE1SOHdIUVlEVlFRRERCWk8KUjBsT1dFbHVaM0psYzNORGIyNTBjbTlzYkdWeU1CNFhEVEU0TURreE1qRTRNRE16TlZvWERUSXpNRGt4TVRFNApNRE16TlZvd0lURWZNQjBHQTFVRUF3d1dUa2RKVGxoSmJtZHlaWE56UTI5dWRISnZiR3hsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUwvN2hIUEtFWGRMdjNyaUM3QlBrMTNpWkt5eTlyQ08KR2xZUXYyK2EzUDF0azIrS3YwVGF5aGRCbDRrcnNUcTZzZm8vWUk1Y2Vhbkw4WGM3U1pyQkVRYm9EN2REbWs1Qgo4eDZLS2xHWU5IWlg0Rm5UZ0VPaStlM2ptTFFxRlBSY1kzVnNPazFFeUZBL0JnWlJVbkNHZUtGeERSN0tQdGhyCmtqSXVuektURXUyaDU4Tlp0S21ScUJHdDEwcTNRYzhZT3ExM2FnbmovUWRjc0ZYYTJnMjB1K1lYZDdoZ3krZksKWk4vVUkxQUQ0YzZyM1lma1ZWUmVHd1lxQVp1WXN2V0RKbW1GNWRwdEMzN011cDBPRUxVTExSakZJOTZXNXIwSAo1TmdPc25NWFJNV1hYVlpiNWRxT3R0SmRtS3FhZ25TZ1JQQVpQN2MwQjFQU2FqYzZjNGZRVXpNQ0F3RUFBVEFOCkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWpLb2tRdGRPcEsrTzhibWVPc3lySmdJSXJycVFVY2ZOUitjb0hZVUoKdGhrYnhITFMzR3VBTWI5dm15VExPY2xxeC9aYzJPblEwMEJCLzlTb0swcitFZ1U2UlVrRWtWcitTTFA3NTdUWgozZWI4dmdPdEduMS9ienM3bzNBaS9kclkrcUI5Q2k1S3lPc3FHTG1US2xFaUtOYkcyR1ZyTWxjS0ZYQU80YTY3Cklnc1hzYktNbTQwV1U3cG9mcGltU1ZmaXFSdkV5YmN3N0NYODF6cFErUyt1eHRYK2VBZ3V0NHh3VlI5d2IyVXYKelhuZk9HbWhWNThDd1dIQnNKa0kxNXhaa2VUWXdSN0diaEFMSkZUUkk3dkhvQXprTWIzbjAxQjQyWjNrN3RXNQpJUDFmTlpIOFUvOWxiUHNoT21FRFZkdjF5ZytVRVJxbStGSis2R0oxeFJGcGZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdi91RWM4b1JkMHUvZXVJTHNFK1RYZUprckxMMnNJNGFWaEMvYjVyYy9XMlRiNHEvClJOcktGMEdYaVN1eE9ycXgrajlnamx4NXFjdnhkenRKbXNFUkJ1Z1B0ME9hVGtIekhvb3FVWmcwZGxmZ1dkT0EKUTZMNTdlT1l0Q29VOUZ4amRXdzZUVVRJVUQ4R0JsRlNjSVo0b1hFTkhzbysyR3VTTWk2Zk1wTVM3YUhudzFtMApxWkdvRWEzWFNyZEJ6eGc2clhkcUNlUDlCMXl3VmRyYURiUzc1aGQzdUdETDU4cGszOVFqVUFQaHpxdmRoK1JWClZGNGJCaW9CbTVpeTlZTW1hWVhsMm0wTGZzeTZuUTRRdFFzdEdNVWozcGJtdlFmazJBNnljeGRFeFpkZFZsdmwKMm82MjBsMllxcHFDZEtCRThCay90elFIVTlKcU56cHpoOUJUTXdJREFRQUJBb0lCQVFDZklHbXowOHhRVmorNwpLZnZJUXQwQ0YzR2MxNld6eDhVNml4MHg4Mm15d1kxUUNlL3BzWE9LZlRxT1h1SENyUlp5TnUvZ2IvUUQ4bUFOCmxOMjRZTWl0TWRJODg5TEZoTkp3QU5OODJDeTczckM5bzVvUDlkazAvYzRIbjAzSkVYNzZ5QjgzQm9rR1FvYksKMjhMNk0rdHUzUmFqNjd6Vmc2d2szaEhrU0pXSzBwV1YrSjdrUkRWYmhDYUZhNk5nMUZNRWxhTlozVDhhUUtyQgpDUDNDeEFTdjYxWTk5TEI4KzNXWVFIK3NYaTVGM01pYVNBZ1BkQUk3WEh1dXFET1lvMU5PL0JoSGt1aVg2QnRtCnorNTZud2pZMy8yUytSRmNBc3JMTnIwMDJZZi9oY0IraVlDNzVWYmcydVd6WTY3TWdOTGQ5VW9RU3BDRkYrVm4KM0cyUnhybnhBb0dCQU40U3M0ZVlPU2huMVpQQjdhTUZsY0k2RHR2S2ErTGZTTXFyY2pOZjJlSEpZNnhubmxKdgpGenpGL2RiVWVTbWxSekR0WkdlcXZXaHFISy9iTjIyeWJhOU1WMDlRQ0JFTk5jNmtWajJTVHpUWkJVbEx4QzYrCk93Z0wyZHhKendWelU0VC84ajdHalRUN05BZVpFS2FvRHFyRG5BYWkyaW5oZU1JVWZHRXFGKzJyQW9HQkFOMVAKK0tZL0lsS3RWRzRKSklQNzBjUis3RmpyeXJpY05iWCtQVzUvOXFHaWxnY2grZ3l4b25BWlBpd2NpeDN3QVpGdwpaZC96ZFB2aTBkWEppc1BSZjRMazg5b2pCUmpiRmRmc2l5UmJYbyt3TFU4NUhRU2NGMnN5aUFPaTVBRHdVU0FkCm45YWFweUNweEFkREtERHdObit3ZFhtaTZ0OHRpSFRkK3RoVDhkaVpBb0dCQUt6Wis1bG9OOTBtYlF4VVh5YUwKMjFSUm9tMGJjcndsTmVCaWNFSmlzaEhYa2xpSVVxZ3hSZklNM2hhUVRUcklKZENFaHFsV01aV0xPb2I2NTNyZgo3aFlMSXM1ZUtka3o0aFRVdnpldm9TMHVXcm9CV2xOVHlGanIrSWhKZnZUc0hpOGdsU3FkbXgySkJhZUFVWUNXCndNdlQ4NmNLclNyNkQrZG8wS05FZzFsL0FvR0FlMkFVdHVFbFNqLzBmRzgrV3hHc1RFV1JqclRNUzRSUjhRWXQKeXdjdFA4aDZxTGxKUTRCWGxQU05rMXZLTmtOUkxIb2pZT2pCQTViYjhibXNVU1BlV09NNENoaFJ4QnlHbmR2eAphYkJDRkFwY0IvbEg4d1R0alVZYlN5T294ZGt5OEp0ek90ajJhS0FiZHd6NlArWDZDODhjZmxYVFo5MWpYL3RMCjF3TmRKS2tDZ1lCbyt0UzB5TzJ2SWFmK2UwSkN5TGhzVDQ5cTN3Zis2QWVqWGx2WDJ1VnRYejN5QTZnbXo5aCsKcDNlK2JMRUxwb3B0WFhNdUFRR0xhUkcrYlNNcjR5dERYbE5ZSndUeThXczNKY3dlSTdqZVp2b0ZpbmNvVlVIMwphdmxoTUVCRGYxSjltSDB5cDBwWUNaS2ROdHNvZEZtQktzVEtQMjJhTmtsVVhCS3gyZzR6cFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
namespace: nginx-ingress
data:
server-names-hash-bucket-size: "1024"
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nginx-ingress
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- update
- create
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- watch
- get
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- k8s.nginx.org
resources:
- virtualservers
- virtualserverroutes
verbs:
- list
- watch
- get
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nginx-ingress
subjects:
- kind: ServiceAccount
name: nginx-ingress
namespace: nginx-ingress
roleRef:
kind: ClusterRole
name: nginx-ingress
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ingress
namespace: nginx-ingress
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9113"
spec:
selector:
matchLabels:
app: nginx-ingress
template:
metadata:
labels:
app: nginx-ingress
spec:
serviceAccountName: nginx-ingress
containers:
- image: nginx/nginx-ingress:1.5.5
name: nginx-ingress
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: prometheus
containerPort: 9113
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
args:
- -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
#- -v=3 # Enables extensive logging. Useful for troubleshooting.
#- -report-ingress-status
#- -external-service=nginx-ingress
#- -enable-leader-election
- -enable-prometheus-metrics
#- -enable-custom-resources
卸载ingress congroller
在master节点上执行
1 | #只在 master 节点执行 |
配置域名解析
将域名 *.demo.yourdomain.com 解析到 demo-worker-a-2 的 IP 地址 z.z.z.z (也可以是 demo-worker-a-1 的地址 y.y.y.y)
验证配置
在浏览器访问 a.demo.yourdomain.com,将得到 404 NotFound 错误页面
提示
许多初学者在安装 Ingress Controller 时会碰到问题,请不要灰心,可暂时跳过 安装 Ingress Controller 这个部分,等您学完 www.kuboard.cn 上 Kubernetes 入门 以及 通过互联网访问您的应用程序 这两部分内容后,再来回顾 Ingress Controller 的安装。
也可以参考 Install Nginx Ingress
Kubernetes集群的设计目标是setup-and-run-forever,然而许多学习者使用自己笔记本上的虚拟机安装K8S集群用于学习,这就必然会出现反复重启集群所在虚拟机的情况。本文针对重启后会出现一些的一些令人困惑的问题做了解释。
Worker节点不能启动
Master 节点的 IP 地址变化,导致 worker 节点不能启动。请重装集群,并确保所有节点都有固定内网 IP 地址。
许多Pod一直Crash或不能正常访问
1 | kubectl get pods --all-namespaces |
重启后会发现许多 Pod 不在 Running 状态,此时,请使用如下命令删除这些状态不正常的 Pod。通常,您的 Pod 如果是使用 Deployment、StatefulSet 等控制器创建的,kubernetes 将创建新的 Pod 作为替代,重新启动的 Pod 通常能够正常工作。
1 | kubectl delete pod <pod-name> -n <pod-namespece> |
1 | sysctl -p |
解决办法1
modprobe br_netfilter
查看版本1
kubectl version
怎么查看可安装的docker版本1
yum list docker-ce --showduplicates|sort -r
https://kuboard.cn/install/install-k8s.html#%E6%A3%80%E6%9F%A5-centos-hostname
https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/troubleshooting-kubeadm/
下载好后,分别解压到/opt目录下
这里如果是单机kafka,可以直接用kafka自带的,启动方式为1
./$KAFKA_HOME/bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
1 | broker.id=0 |
大多数情况下,可能会改动的估计就是上述3个配置,由于kafka需要做持久化消息,故这里有一个log.dirs的配置项
1 | ./bin/kafka-server-start.sh -daemon config/server.properties |
不加-daemon则是前台启动,若是在docker中启动,就不用加-daemon
依赖的组件说明
cmak 3.x需要依赖jdk 11+ 且zookeeper需要3.5.x版本,我直接用了es 7.x的自带jdk13
所以修改了cmak启动脚本get_java_cmd函数:1
2
3
4
5
6
7
8
9
10
11
12 Detect if we should use JAVA_HOME or just try PATH.
get_java_cmd() {
High-priority override for Jlink images
if [[ -n "$bundled_jvm" ]]; then
echo "$bundled_jvm/bin/java"
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
echo "$JAVA_HOME/bin/java"
else
echo "java"
fi
echo "/opt/elasticsearch-7.6.1/jdk/bin/java"
}
修改配置
修改conf/application.conf
1 | kafka-manager.zkhosts="localhost:2181" |
当然也可以不修改application.conf文件,而是提供环境变量ZK_HOSTS
启动cmak
1 | ./bin/cmak > cmak.out 2>&1 & |
添加集群
点添加集群(Add Cluster)
填写集群名称与zookeeper地址
添加完成查看集群信息,包括zookeepers连接地址,kafka版本,topic数量,broker数量
查看broker信息
查看、操作topic
之前已经创建过first-topic,topics列表就可以看到了
给topic增加分区
创建topic
PS:更多cmak的功能有待实践
常见错误1
This application is already running (Or delete /opt/kafka-cmak-3.0.0.4/RUNNING_PID file).
删掉RUNNING_PID重新启动
创建Topic
1 | root@ubuntu:/opt/kafka_2.12-2.4.0# ./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic first-topic |
https://segmentfault.com/a/1190000012730949
https://blog.wolfogre.com/posts/kafka-manager-download/
1 | groupadd es && useradd es -g es -M -s /bin/bash |
1 | vm.max_map_count=262144 |
使得重启之后也生效,即永久生效
执行如下命令使得立即生效
1 | sysctl –p |
修改/etc/security/limits.conf添加以下配置
1 | es hard nofile 65535 |
/etc/systemd/user.conf和/etc/systemd/system.conf分别添加以下配置
1 | DefaultLimitNOFILE=65535 |
systemd启动elasticsearch,需要设置这两个文件,否则启动会报如下错误
[1] bootstrap checks failed
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
1 | tar xzvf elasticsearch-7.6.1-linux-x86_64.tar.gz -C /opt/ |
1主2从的配置
1 | cluster.name: my-es |
1 | cluster.name: my-es |
1 | cluster.name: my-es |
生产环境建议network.host: 指定IP
1 | curl -XGET http://192.168.56.131:9200/_cluster/health?pretty |
方案一 在/etc/init.d目录下
创建名为elasticsearch的文件,内容如下
1 | !/bin/sh |
1 | chmod a+x elasticsearch.sh |
方案二 在/lib/systemd/system目录下
创建名为elasticsearch.service的文件,内容如下1
2
3
4
5
6
7
8
9
10
11
12
13[Unit]
Description=elasticsearch
After=network.target
[Service]
Type=simple
ExecStart=/bin/bash /opt/elasticsearch-7.6.1/bin/elasticsearch
Restart=always
User=es
Group=es
WorkingDirectory=/opt/elasticsearch-7.6.1
[Install]
WantedBy=mutil-user.target
重载配置文件1
sudo systemctl daemon-reload
启用服务1
sudo systemctl enable elasticsearch
启动服务1
sudo systemctl start elasticsearch
如果启动失败,想看下日志
1 | 查看状态 |
通过systemctl 启动服务报如下错误1
2
3
4
5
6
7Mar 15 10:08:51 ubuntu bash[1308]: ERROR: [1] bootstrap checks failed
Mar 15 10:08:51 ubuntu bash[1308]: [1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
Mar 15 10:08:51 ubuntu bash[1308]: ERROR: Elasticsearch did not exit normally - check the logs at /opt/elasticsearch-7.6.1/logs/my-es.log
Mar 15 10:08:51 ubuntu bash[1308]: [2020-03-15T02:08:51,352][INFO ][o.e.n.Node ] [node-1] stopping ...
Mar 15 10:08:51 ubuntu bash[1308]: [2020-03-15T02:08:51,462][INFO ][o.e.n.Node ] [node-1] stopped
Mar 15 10:08:51 ubuntu bash[1308]: [2020-03-15T02:08:51,463][INFO ][o.e.n.Node ] [node-1] closing ...
Mar 15 10:08:51 ubuntu bash[1308]: [2020-03-15T02:08:51,535][INFO ][o.e.n.Node ] [node-1] closed
划重点:通过systemctrl自建elasticsearch系统服务的形式来启动的es,则需要修改/etc/systemd/user.conf,/etc/systemd/system.conf这两个文件,添加以下配置1
DefaultLimitNOFILE=65536
1 | org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root |
这个是最常见的了,不能用root用户来启动elasticsearch
1 | Exception in thread "main" org.elasticsearch.bootstrap.BootstrapException: java.nio.file.AccessDeniedException: /opt/elasticsearch-7.6.1/config/elasticsearch.keystore |
处理方法 chown -R es:es $ES_HOME/conf
1 | 2020-03-07 14:50:20,774 main ERROR Unable to invoke factory method in class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender |
处理方法 chown -R es:es $ES_HOME/logs
1 | [2020-03-07T14:11:03,119][WARN ][o.e.c.c.Coordinator ] [node-1] failed to validate incoming join request from node [{node-2}{OTFjHR3wRuyQBMbb9PdGoQ}{GH_dXyYZQIuyjnnBTsELRw}{192.168.56.132}{192.168.56.132:9300}{dil}{ml.machine_memory=3665874944, ml.max_open_jobs=20, xpack.installed=true}] |
如果集群所有节点cluster.name设置都一样的,但还是报以上错误,可以将集群内的节点的data目录删掉后重启
目前es没有桌面客户端,不过elasticsearch-head算是比较方便的,用于快速查询下数据。
这里我就给出elastic-head,systemd服务的脚本
1 | [Unit] |
请事先安装好node环境,并在/opt/elasticsearch-head目录,执行好npm i -V安装好相关module
执行如下命令,下次就能开机启动了1
2 systemctl daemon-reload
systemctl enable elasticsearch-head
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docker.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html
https://www.linuxtechi.com/set-ulimit-file-descriptors-limit-linux-servers/
https://askubuntu.com/questions/1102512/set-ulimit-for-non-root-user
https://superuser.com/questions/1200539/cannot-increase-open-file-limit-past-4096-ubuntu/1200818#1200818
http://www.ruanyifeng.com/blog/2018/03/systemd-timer.html
title: 如何构建SpringBoot应用的Docker镜像
date: 2019-12-04 22:17:54
categories:
SpringBoot大道其行,微服务也是大道其行,越来越多的SpringBoot应用使用Docker容器来运行。既然要用Docker容器运行,那首先得把SpringBoot应用打包成Docker镜像。
目前主要的方法有三种:
下面我们一一来介绍,首先来介绍
项目地址:https://github.com/spotify/dockerfile-maven
以下给出了多个Dockerfile,根据实际情况选择
简洁版
1 | FROM openjdk:8-jdk-alpine |
有带-D的参数
1 | FROM openjdk:8-jdk-alpine |
常见的场景如excel导出,验证码
1 | FROM openjdk:8-jdk-alpine |
1 | <build> |
docker.image.prefix可以预先在properties标签中定义好,如果要区分不同环境传到不同的镜像中心可使用profiles标签来定义不同环境的docker.image.prefix
前提是要先安装好docker服务
1 | mvn clean package -Dmaven.test.skip=true -U |
项目地址:https://github.com/GoogleContainerTools/jib
Google Jib的优势是无需事先安装docker,无需定义Dockerfile文件,而且Google Jib可以分层,依赖的jar构成一层,resource构成一层,然后项目本身的classes构建一层。这样如果只是改变项目本身代码,那么构建是非常快的。且推送的镜像也是非常小的,可以大大节省网络流量。
1 | <build> |
lombok.version为1.18.8
mapstruct.version为1.3.0.Final
如果您的项目没有用到Pinpoint,Nacos可以完全精简为如下
1 | <build> |
这里我们构建了一个基镜像harbor.chaomeifan.com/library/openjdk:8-jdk-alpine-v1,以下给出基镜像的Dockerfile
1 | FROM openjdk:8-jdk-alpine |
由于基镜像用了非root用户启动进程,故如果有映射到容器的目录需要将目录所有者所属组改为Dockerfile中定义的用户id,组id,chown -R 1739:1739 /path/directory
如果觉得嫌麻烦,可以在基镜像中直接用root
1 | FROM openjdk:8-jdk-alpine |
这样就无需在jib插件配置中设置
zcckj:zcckj
1 | mvn clean compile jib:build -Dmaven.test.skip=true -DsendCredentialsOverHttp=true -U |
如果系统之前安装过mysql,版本可能是比较旧的,需要在安装之前确认下。可以使用如apt,yum包管理工具,将mysql卸载。并注意/etc/my.cnf,mysql数据存放目录,将数据备份好,然后再删除这些文件或目录。
mysql需要依赖libaio库,在安装mysql前确认已经安装好libaio库
centos:
1 | yum search libaio # search for info |
ubuntu:
1 | apt-cache search libaio # search for info |
下载64位mysql5.7.27 linux通用二进制安装包1
wget --no-check-certificate https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.27-linux-glibc2.12-x86_64.tar.gz
二进制安装包清单
目录 | 目录中的文件 |
---|---|
bin | mysqld服务端程序,mysql客户端程序,等其他工具程序 |
docs | mysql文档 |
man | man手册 |
include | header头文件 |
share | 用于数据库安装的错误消息、字典和SQ |
support-files | 各种支持文件 |
1 | groupadd mysql |
此用户仅仅用于mysql服务,而不用于系统登录,所以使用useradd -r和-s /bin/false命令选项来创建没有登录权限的用户。
解压mysql安装包到/user/local目录下,并设置软连接,使得mysql的basedir为/usr/local/mysql1
2
3tar zxvf /path/to/mysql-5.7.27-linux-glibc2.12-x86_64.tar.gz -C /usr/local
cd /usr/local
ln -s mysql-5.7.26-linux-glibc2.12-x86_64 mysql
/path/to改为mysql安装包所在目录
将bin目录添加到PATH环境变量中
1 | export PATH=$PATH:/usr/local/mysql/bin |
名称 | 目录或文件 | 软链 | 实际目录 |
---|---|---|---|
数据目录datadir | /data/mysql/data | 否 | |
参数配置my.cnf | /usr/local/mysql/etc/my.cnf | 否 | |
日志log目录 | /usr/local/mysql/log | 是 | /data/mysql/log |
错误日志log-error | /usr/local/mysql/log/mysql_error.log | 否 | |
慢查询日志slow_query_log_file | /usr/local/mysql/log/mysql_slow_query.log | 否 | |
二进制日志log-bin | /usr/local/mysql/binlogs/mysql-bin | 是 | /data/mysql/binlogs/mysql-bin |
套接字socket文件 | /usr/local/mysql/run/mysql.sock | 否 | |
pid文件(进程id) | /usr/local/mysql/run/mysql.pid | 否 |
默认情况下socket文件, pid文件是存在datadir的,建议还是分开一个目录,取名run目录好一点
1 | mkdir -p /usr/local/mysql/{etc,run} |
文件放在/usr/local/mysql/etc/my.cnf,放此目录,会自动加载该配置文件的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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
[client]
port = 3306
socket = /usr/local/mysql/run/mysql.sock
[mysqld]
##########################
#GENERAL
##########################
user = mysql
port = 3306
server_id = 1
skip-name-resolve
bind-address= 0.0.0.0
default_storage_engine = InnoDB
socket = /usr/local/mysql/run/mysql.sock
pid-file = /usr/local/mysql/run/mysqld.pid
max_allowed_packet=32M
max_connections = 1024
open_files_limit = 65535
#default-time-zone='+08:00'
explicit_defaults_for_timestamp=true
##########################
# character set
##########################
character_set_server = utf8mb4
collation-server = utf8mb4_general_ci
init_connect='SET NAMES utf8mb4'
##########################
# DATA STORAGE
##########################
basedir= /usr/local/mysql
datadir = /data/mysql/data
##########################
# INNODB
##########################
innodb_file_per_table = 1
innodb_buffer_pool_size = 512M
innodb_log_buffer_size = 16M
innodb_log_file_size = 128M
innodb_tmpdir= /data/mysql/tmp
##########################
# MyISAM
##########################
key_buffer_size = 64M
##########################
# Logging
##########################
# mysql log timezone use system timezone
log_timestamps=SYSTEM
log_error = /data/mysql/log/mysql_error.log
##########################
# log bin
##########################
log-bin = /data/mysql/binlogs/mysql-bin
sync-binlog = 1
binlog_format = row
binlog_row_image = FULL
expire_logs_days = 15
master_info_repository = TABLE
relay_log_info_repository = TABLE
##########################
# log relay
##########################
relay-log = /data/mysql/binlogs/relay-bin
relay_log_recovery = on
max_relay_log_size = 1G
log_slave_updates = 1
##########################
# gtid
##########################
gtid_mode = on
enforce_gtid_consistency = on
replicate_ignore_db=mysql
replicate_ignore_db=information_schema
replicate_ignore_db=performation_schema
replicate_ignore_db=sys
##########################
# General logs (only enable for debugging – it use too much I/O)
##########################
#general-log = on
#general-log-file = /data/mysql/log/general-query.log
##########################
# Slow query logs (optional)
##########################
slow_query_log = on
long_query_time= 3
slow_query_log_file = /data/mysql/log/slow-query.log
查看初始化参数1
mysqld --verbose --help |more
1 | cd /user/local/mysql |
在日志文件理会提示一个临时密码,记录这个密码
1 | grep 'temporary password' /data/mysql/log/mysql_error.log |
1 | mysql_ssl_rsa_setup --basedir=/usr/local/mysql --datadir=/data/mysql/data |
1 | mysqld_safe --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql/data --defaults-file=/usr/local/mysql/etc/my.cnf & |
或
1 | mysqld_safe --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql/data & |
可以不用指定–defaults-file,会加载到$MYSQL_HOME/etc/my.cnf这个配置文件
mysql -uroot -p
输入上一步查到的临时密码
1 | set password=password('123456'); |
1 | Next command is optional |
修改/etc/init.d/mysqld的basedir,datadir等配置
chomod 755 /etc/init.d/mysqld
下面介绍使用 systemctrl来管理mysql服务
创建 /etc/systemd/system/mysqld.service1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17[Unit]
Description=MySQL Server
Documentation=man:mysqld(5.7)
Documentation=https://dev.mysql.com/doc/refman/5.7/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
#PIDFile=/usr/local/mysql/run/mysqld.pid
#ExecStart=
ExecStart=/usr/local/mysql/bin/mysqld_safe --defaults-file=/usr/local/mysql/etc/my.cnf
LimitNOFILE=65535
设置开机启动1
2systemctl daemon-reload
systemctl enable mysqld
启动、停止、查看日志1
2
3systemctrl start mysqld
systemctrl stop mysqld
journalctl -u mysqld
单机多实例的安装配置见https://www.jb51.net/article/103843.htm,需要注意版本差异
https://lalitvc.wordpress.com/2019/03/04/mysql-5-7-binary-install-on-linux/
https://dev.mysql.com/doc/refman/5.7/en/binary-installation.html
https://dev.mysql.com/doc/refman/5.7/en/postinstallation.html
https://dev.mysql.com/doc/refman/5.7/en/using-systemd.html#systemd-mysql-configuration
https://dev.mysql.com/doc/refman/5.7/en/mysqld-safe.html#option_mysqld_safe_malloc-lib
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html
https://blog.csdn.net/zml3721/article/details/79090983
https://www.jianshu.com/p/0d628b2f7476
https://www.jb51.net/article/103843.htm
https://blog.pythian.com/manage-multiple-mysql-binary-installations-with-systemd/
https://www.linuxidc.com/Linux/2017-10/147829.htm
https://blog.csdn.net/l1028386804/article/details/87996449
自己搭建的图片服务器,有图片缩放的需求,大致思路是可以使用nginx调用lua,使用GraphicMagick的命令来做图片缩放
lua.jaychang.cn(如/var/filebase)
1 | jaychang@nginx:~$ tree /var/filebase/ |
其中img.xyz.com为图片站点根目录
cache/thumb为缩略图存放目录
upload目录存放上传的图片
原图访问地址:http://img.xyz.com/upload/1.png
缩略图访问地址:http://img.xyz.com/upload/1.png_100x100.png 即为宽100,高100
自动宽地址: http://img.xyz.com/upload/1.png_-400.png 用”-“表示自动,即为高400,宽自动
自动高地址: http://img.xyz.com/upload/1.png_800-.jpg 用”-“表示自动,即为宽800,高自动
首先判断缩略图是否存在,如存在则直接显示缩略图;
缩略图不存在,则判断原图是否存在,如原图存在则拼接graphicsmagick(gm)命令,生成并显示缩略图,否则返回404
lua-nginx-module 依赖于 LuaJIT 和 ngx_devel_kit。LuaJIT 需要安装,ngx_devel_kit 只需下载源码包,在 Nginx 编译时指定 ngx_devel_kit 目录
1 | curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz |
1 | apt-get install -y gcc g++ make |
1 | curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz |
1 | curl -R -O http://luajit.org/download/LuaJIT-2.0.5.tar.gz |
1 | apt install -y graphicsmagick |
直接用apt来安装的话,可以免去安装 jpg,png 等图片库依赖
1 | groupadd nginx && useradd nginx -g nginx -s /sbin/nologin -M |
创建用户也可以用以下命令:1
groupadd nginx && useradd nginx -g nginx -s /bin/false -M
1 | ./configure --prefix=/usr/local/nginx \ |
1 | make -j 2 && make install |
注意:动态加载模块,Nginx官方的load_module指令,详细文档参考1
和 参考2
还有–with-http_spdy_module 已经改为–with-http_v2_module了
,如果不用geo的话,编译的时候可以不加–with-http_geoip_module,可以不安装libgeoip-dev
1 | nginx -t |
如果出现以下错误(没有报错就不用做以下操作了)1
2root@ubuntu:/usr/local/src/nginx-1.14.2# nginx -t
nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
执行以下命令即可1
2echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
ldconfig
将nginx.conf配置文件中的server{}段配置注释掉
加一行配置,以便读取/usr/local/tengine/conf.d目录下所有后缀为.conf的配置文件1
include ../conf.d/*.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
50
51
52
53user nginx;
worker_processes 4;
worker_cpu_affinity 1000 0100 0010 0001;
error_log /var/log/nginx/error.log error;
pid /usr/local/nginx/logs/nginx.pid;
worker_rlimit_nofile 65535;
events
{
use epoll;
worker_connections 65535;
}
http
{
lua_load_resty_core off;
limit_conn_zone $binary_remote_addr zone=one:10m;
limit_conn_zone $server_name zone=perserver:10m;
include mime.types;
include fastcgi.conf;
default_type application/octet-stream;
charset utf-8;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 64k;
sendfile on;
autoindex off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 60;
fastcgi_read_timeout 60;
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
client_max_body_size 10m;
include ../conf.d/*.conf;
}
注意:lua_load_resty_core off;如果不加会有以下错误
1 | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html) |
目录规划
1 | mkdir -p /var/filebase/upload |
在/usr/local/tengine/conf.d目录下创建demo.conf
1 | # 定义lua缩略图支持的图片尺寸及开关 |
1 | -- 根据输入长或宽的尺寸自动裁切图片大小 |
1 | -- 根据输入长和宽的尺寸裁切图片 |
然后 nginx -s reload下
在/var/filebase/upload目录,放1张图片
原图
100x100
由于我们开启了image_sizes_check,不支持的尺寸会返回404
固定定宽高自适应
查看服务器上生成的文件
PS:这里有个问题,就是图片缩放后,图片的方向跟原先不一致的问题,后来笔者查了下GraphicsMagick资料,关于图片缩放后改变图片方向的问题,可以用-auto-orient参数可以来解决的,可以写成”gm convert -auto-orient “
https://github.com/botaozhao/nginx-lua-GraphicsMagick
https://www.fanhaobai.com/2017/09/lua-in-nginx.html
https://github.com/openresty/lua-nginx-module/issues/1533
https://github.com/openresty/lua-nginx-module/pull/1501#issuecomment-486123650
https://github.com/openresty/lua-resty-core/issues/248
https://segmentfault.com/a/1190000011093243
https://github.com/yanue/nginx-lua-GraphicsMagick/blob/master/nginx-install.md
http://www.icode9.com/content-3-77987.html
https://my.oschina.net/ranhai/blog/1797454
https://www.twblogs.net/a/5bafdb372b7177781a0f64d7/zh-cn
自己搭建的图片服务器,有图片缩放的需求,大致思路是可以使用nginx调用lua,使用GraphicMagick的命令来做图片缩放
lua.jaychang.cn(如/var/filebase)
1 | jaychang@tengine:~$ tree /var/filebase/ |
其中img.xyz.com为图片站点根目录
cache/thumb为缩略图存放目录
upload目录存放上传的图片
原图访问地址:http://img.xyz.com/upload/1.png
缩略图访问地址:http://img.xyz.com/upload/1.png_100x100.png 即为宽100,高100
自动宽地址: http://img.xyz.com/upload/1.png_-400.png 用”-“表示自动,即为高400,宽自动
自动高地址: http://img.xyz.com/upload/1.png_800-.jpg 用”-“表示自动,即为宽800,高自动
首先判断缩略图是否存在,如存在则直接显示缩略图;
缩略图不存在,则判断原图是否存在,如原图存在则拼接graphicsmagick(gm)命令,生成并显示缩略图,否则返回404
1 | apt-get install -y gcc g++ make |
1 | curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz |
1 | curl -R -O http://luajit.org/download/LuaJIT-2.0.5.tar.gz |
1 | wget ftp://ftp.graphicsmagick.org/pub/GraphicsMagick/1.3/GraphicsMagick-1.3.31.tar.gz |
1 | groupadd nginx && useradd nginx -g nginx -s /sbin/nologin -M |
创建用户也可以用以下命令:1
groupadd nginx && useradd nginx -g nginx -s /bin/false -M
1 | ./configure --prefix=/usr/local/tengine \ |
1 | make -j 2 && make install |
注意:在Tengine-2.3.0版本后废弃Tengine的dso_tool工具以及dso配置指令,若之前有使用Tengine的dso功能、则可以切换到Nginx官方的load_module指令,详细文档参考1
和 参考2
还有–with-http_spdy_module 已经改为–with-http_v2_module了
,如果不用geo的话,编译的时候可以不加–with-http_geoip_module,可以不安装libgeoip-dev
1 | cd /usr/local/tengine |
如果出现以下错误(没有报错就不用做以下操作了)1
2jaychang@tengine:/usr/local/tengine/sbin$ ./nginx -t
./nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
查找下位置1
2
3
jaychang@tengine:/usr/local/tengine/sbin$ sudo whereis libluajit-5.1.so.2
libluajit-5.1.so: /usr/local/lib/libluajit-5.1.so /usr/local/lib/libluajit-5.1.so.2
修改/etc/ld.so.conf,添加“/usr/local/lib”
vi /etc/ld.so.conf1
2include /etc/ld.so.conf.d/*.conf
/usr/local/lib
然后执行下ldconfig1
ldconfig
将nginx.conf配置文件中的server{}段配置注释掉
加一行配置,以便读取/usr/local/tengine/conf.d目录下所有后缀为.conf的配置文件1
include ../conf.d/*.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
50
51
52user nginx;
worker_processes 4;
worker_cpu_affinity 1000 0100 0010 0001;
error_log /var/log/tengine/error.log error;
pid /usr/local/tengine/logs/nginx.pid;
worker_rlimit_nofile 65535;
events
{
use epoll;
worker_connections 65535;
}
http
{
limit_conn_zone $binary_remote_addr zone=one:10m;
limit_conn_zone $server_name zone=perserver:10m;
include mime.types;
include fastcgi.conf;
default_type application/octet-stream;
charset utf-8;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 64k;
sendfile on;
autoindex off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 60;
fastcgi_read_timeout 60;
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
client_max_body_size 10m;
include ../conf.d/*.conf;
}
目录规划
1 | mkdir -p /var/filebase/upload |
在/usr/local/tengine/conf.d目录下创建demo.conf
1 | # 定义lua缩略图支持的图片尺寸及开关 |
1 | -- 根据输入长或宽的尺寸自动裁切图片大小 |
1 | -- 根据输入长和宽的尺寸裁切图片 |
然后 nginx -s reload下
在/var/filebase/upload目录,放1张图片
原图
100x100
由于我们开启了image_sizes_check,不支持的尺寸会返回404
固定定宽高自适应
查看服务器上生成的文件
PS:这里有个问题,就是图片缩放后,图片的方向跟原先不一致的问题,后来笔者查了下GraphicsMagick资料,关于图片缩放后改变图片方向的问题,可以用-auto-orient参数可以来解决的,可以写成”gm convert -auto-orient “
https://github.com/botaozhao/nginx-lua-GraphicsMagick
https://github.com/yanue/nginx-lua-GraphicsMagick/blob/master/nginx-install.md
http://www.icode9.com/content-3-77987.html
https://my.oschina.net/ranhai/blog/1797454
https://www.twblogs.net/a/5bafdb372b7177781a0f64d7/zh-cn
Swarm 是 Docker 官方提供的一款集群管理工具,用来管理 Docker 集群,它将若干台 Docker 主机抽象为一个整体,并且通过一个入口统一管理这些 Docker 主机上的各种 Docker 资源。
Swarm 只是一个调度器(Scheduler)加路由器 (Router),Swarm 自己不运行容器,它只是接受 Docker 客户端发送过来的请求,调度适合的节点来运行容器,这意味着,即使 Swarm 由于某些原因挂掉了,集群中的节点也会照常运行,当 Swarm 重新恢复运行之后,它会收集重建集群信息。
Docker Engine 从 V1.12.0 版本开始,原生集成了 Docker Swarm,所以只要在每台机器上安装 Docker,就可以直接使用 Docker Swarm。
Swarm 和 Kubernetes 比较类似,但是更加轻,具有的功能也较 Kubernetes 更少一些。
Swarm会提供网络管理、负载均衡、基于DNS容器发现、集群状态管理、扩容缩容等功能,在DevOps流程层面,可以组合成发布、回滚应用、查看服务的状态、日志、终端,更改发布配置(例如环境变量、默认副本数等)等功能。这个层面Portainer这个开源项目,提供了更好用集群管理的UI界面和类似Kubernetes Apiserver设计的Docker集群API接口服务。
Docker Swarm的资源抽象比较简单,在没有容器编排的情况下,我们主要使用Services,这个和Kubernetes上的Services有些不一样,可以理解为Services和Deployment的合体,用来管理和定义容器的调度和扩容等,也可以直接做端口映射,在容器集群里任意IP都可以访问。
因为Manager节点只负责调度集群的Task,配置要求不高,可用3台低配置机器作为Manager节点,根据Raft算法,3台可以容许1台机器出问题,5台可以允许2台机器出问题,大家可以按需求部署。Worker节点的作用是运行业务容器。同时,在Manager和Worker节点上,会以容器方式部署机器监控和日志收集,上面还需要运行网络和存储插件。
IP地址 | Swarm节点类型 | 主机名 |
---|---|---|
192.168.56.111 | manager | m01 |
192.168.56.112 | manager | m02 |
192.168.56.113 | manager | m03 |
192.168.56.114 | worker | n01 |
192.168.56.115 | worker | n02 |
笔者是用VirtualBox虚拟5台机器,作为运行Docker的宿主机。本节以下内容均是在m01上操作的。其他节点配置不再赘述,参考此m01节点的配置即可。
每台宿主机需要启用仅主机(Host-Only)网络及网络地址转换(NAT)两张网卡
以m01为例,/etc/netplan/01-netcfg.yaml网络配置如下:1
2
3
4
5
6
7
8
9
10
11network:
version: 2
renderer: networkd
ethernets:
enp0s3:
dhcp4: no
dhcp6: no
addresses: [192.168.56.111/24]
enp0s8:
dhcp4: yes
dhcp6: no
注意这里enp0s3就是仅主机(Host-Only)网络这个网卡,此网卡用于ssh连接我们的虚拟机,由于这里是virtualbox虚拟机无需配置网关,enp0s8就是网络地址转换(NAT)这个网卡,此网卡用于访问外网。
设置主机名 sudo vi /etc/hostname1
m01
如果遇到重启机器后主机名被重置,可以再用sudo hostnamectl set-hostname xxx 设置
通过sudo vi /etc/host以添加以下内容
1 | 192.168.56.111 m01 |
备份sources.list,sudo cp /etc/apt/sources.list /etc/apt/sources.list.default
批量修改sources.list1
2
3sudo sed -i "s#us.archive.ubuntu.com/#mirrors.aliyun.com/#g" /etc/apt/sources.list
sudo sed -i "s#security.ubuntu.com/#mirrors.aliyun.com/#g" /etc/apt/sources.list
1 | curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add - |
1 | sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" |
1 | sudo apt-get -y update |
若想要安装指定版本Docker,参照如下操作:
1 |
|
1 | sudo apt-get -y install docker-ce=[VERSION] |
注意上面命令中的[VERSION]需要替换成你想要安装的docker版本,
比如这里我将[VERSION]替换为5:18.09.4~3-0~ubuntu-bionic,所以最终的安装命令是sudo apt-get -y install docker-ce=5:18.09.6~3-0~ubuntu-bionic其他可选的Docker软件源还有清华大学Docker软件源
1 | sudo vi /etc/docker/daemon.json |
注意 insecure-registries是配置非https的镜像中心,比如我们自己搭建的一个docker私有镜像中心,是用http的话,就可以用insecure-registries指定。
如果碰到Unable to proxy the request via the Docker socket,这个错误,建议可以在任意worker节点,docker服务配置,
这里只要改集群中一个节点就可以了(如果是用portainer agent方式添加的endpoint)主要是要改ExecStart,暴露2375,笔者改的是n01节点上的/lib/systemd/system/docker.service
sudo vi /lib/systemd/system/docker.service
1 | [Unit] |
还可以改成1
2# for containers run by docker
ExecStart=/usr/bin/dockerd -H unix:// -H 0.0.0.0:2375 --containerd=/run/containerd/containerd.sock
或
1 | # for containers run by docker |
sudo systemctl daemon-reload
sudo systemctl restart docker
1 | sudo systemctl restart docker |
上述都是在做准备工作,接下来就正式开始搭建Swarm集群。
1 | sudo docker swarm init --advertise-addr 192.168.56.111 |
注意:上图显示的token仅作参考,具体需看实际情况,也请记下来
如果遗失可以通过下面两个命令,分别找回manager、worker的join token
1 | sudo docker swarm join-token manager |
根据上一节给出的提示,在192.168.56.111(m01)上执行1
sudo docker swarm join-token manager
根据提示,若要将节点作为manager节点加入swarm集群,需执行如下命令:1
docker swarm join --token SWMTKN-1-65q3om1lmrjrsvnbqug0qccf00ertneorskvmv86g05qzicc8w-1kuf57afgawuuz24h7gx4hycb 192.168.56.111:2377
根据我们的规划,我们要将m02、m03作为manager节点加入到swarm集群,所以我们在m02,m03上执行:
1 | sudo docker swarm join --token SWMTKN-1-65q3om1lmrjrsvnbqug0qccf00ertneorskvmv86g05qzicc8w-1kuf57afgawuuz24h7gx4hycb 192.168.56.111:2377 |
注意:请根据实际情况进行修改,不要一模一样复制!
分别在n01,n02上执行如下命令:
1 | docker swarm join --token SWMTKN-1-65q3om1lmrjrsvnbqug0qccf00ertneorskvmv86g05qzicc8w-978befgfbbuqzqzv2zbvcg8fk 192.168.56.111:2377 |
注意:请根据实际情况进行修改,不要一模一样复制!
在任意manager节点上执行1
sudo docker node ls
从上图可以看到集群中有3个manager节点,2个worker
建议每次都在固定的节点上运行,所以这里指定了node.hostname == m011
sudo docker service create --name portainer --publish 9000:9000 --replicas=1 --constraint 'node.hostname == m01' --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock --mount type=volume,src=portainer_data,dst=/data portainer/portainer
添加一个Local Endpoint,由于portainer是运行在m01上的,所以就是添加m01
打开App Template,点击Poirtainer Agent
给stack取个名字,然后单击deploy
部署完成后,每个节点会起一个
部署Portainer Agent的另外一个途径是在任意manager节点上,执行
以下命令
1 | curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent |
这里以添加m02为例,Endpoint的名称我们就填m02这个主机名好了,然后就是要填写Endpoint URL,就填m02节点的IP地址以及Portainer Agent的端口号,即192.168.56.112:9001
所有Endpoint添加完毕
这里的m01也可以删掉后,用Agent来添加
当镜像中心是需要身份认证的,那么可以在registries里添加一个镜像中心,输入镜像中心的用户名,密码即可,这样集群中的所有节点都能从该镜像中心拉取镜像了。由于篇幅,这里不再赘述跟截图了,读者可以自行尝试。
1 | sudo docker service create --name mynginx --replicas 3 -p 8088:80 nginx:alpine |
我们发现通过任意节点的8088端口都能访问,那是因为Ingress的特性使得请求可以转发到任何一台Worker或者Manager节点上,然后通过内置的routing mesh的Load Balancer通过LVS转发请求到Service的其中一个副本,一个值得注意的点是,就算这台Node上没有运行这个Service的容器。Load Balancer也会找到其他机器上存在的副本,这个主要是通过内置的DNS加LVS转发实现。
1 | sudo docker service scale mynginx=2 |
1 | sudo docker service scale mynginx=3 |
manager节点不能直接删除,需要先降级到worker节点再删除。
比如,这里我误把n02作为manager加入到集群,我想把n02改成worker节点。
1 | sudo docker node demote cu8f |
cu8f为节点ID的前缀
1 | sudo docker node rm [ID或HOSTNAME] |
在工作节点上执行1
sudo docker swarm leave
工作节点离开集群后,节点状态就会变为Down,这样此节点就可以加入到另外一个swarm集群了。(原先的swarm集群manager节点上记得用sudo docker node rm xxx删除此节点)
1 | docker network create --subnet=192.168.10.0/24 --attachable -d overlay my-net |
通过sudo apt-get purge docker-ce,然后将/var/lib/docker目录删掉
http://dockone.io/article/8808 房多多容器化和容器云实践
https://www.cnblogs.com/bigberg/p/8656963.html Docker Macvlan
https://cizixs.com/2017/02/14/network-virtualization-macvlan/ linux 网络虚拟化: macvlan
https://opsx.alibaba.com/guide?lang=zh-CN&document=69a2341e-801e-11e8-8b5a-00163e04cdbb ubuntu 18.04 (bionic) 配置 opsx 安装源
由于笔者所在公司无论是开发、测试环境,还是准生产环境、生产环境,使用的都是CentOS6.6。要想按照新版本的Docker就必须升级Linux内核。因此,此文讲解如何升级Linux内核,以及在升级完内核后,如何安装Docker17.06.2-ce。其他版本的docker笔者没有尝试,应该也是可行的。毕竟内核升级到4.4.132-1了
第一种安装方式,需要联网,因此请确保机器可以连接到互联网
uname -a
cat /etc/centos-release
1 | [root@localhost ~]# uname -a |
rpm –import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
1 | [root@localhost ~]# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org |
yum -y update nss
1 | [root@localhost ~]# yum -y update nss |
rpm –import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
1 | [root@localhost ~]# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org |
rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm
1 | [root@localhost ~]# rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm |
yum –enablerepo=elrepo-kernel install kernel-lt -y
不建议
yum –enablerepo=elrepo-kernel install -y kernel-ml( ml=mainline)
1 | [root@localhost ~]# yum --enablerepo=elrepo-kernel install kernel-lt -y |
vi /etc/grub.conf (这里踩了个坑,我改了/etc/grub.conf文件,但是貌似一直没有生效,后来发现原来用的是/boot/grub/grub.conf,这个得事先观察下是否存在这个/boot/grub/grub.conf),一般情况应该都是使用/etc/grub.conf。但无论如何安装内核都是去操作/etc/grub.conf文件的,如果grub启动用的是/boot/grub/grub.conf,那么请将/etc/grub.conf文件内新安装的内核的启动引导配置复制到/boot/grub/grub.conf文件内。
1 | # grub.conf generated by anaconda |
将default=1改为default=0,确保默认启动新的内核。一般新安装的内核会在第一个位置(从0开始),所以设置default=0
笔者这里用的是二进制安装包来安装
下面给出中科大与阿里云的docker二进制安装包
curl -O http://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-17.06.2-ce.tgz
tar -xzvf docker-17.06.2-ce.tgz && cp docker/* /usr/bin && rm -rf docker
mkdir -p /etc/docker && echo “{\”registry-mirrors\”: [\”https://docker.mirrors.ustc.edu.cn\”]}” > /etc/docker/daemon.json
1 | [root@localhost ~]# curl -O http://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-17.06.2-ce.tgz |
1 | [root@localhost ~]# dockerd |
echo “nohup dockerd > /var/log/dockerd.log & 2>&1” >> /etc/rc.local
1 | [root@localhost ~]# echo "nohup dockerd > /var/log/dockerd.log 2>&1 &" >> /etc/rc.local |
如果看到内核版本已经是4.4.132啦,那说明grub确实已经引导了新的内核了
1 | [root@localhost ~]# uname -a |
此时我们也可以查看docker的日志,我们把日志放在了/var/log/dockerd.log。或者你可以直接运行dockerd。
提示我们cgroup的东东,看来得安装cgroup
1 | [root@localhost ~]# docker info |
yum install -y libcgroup
echo “none /sys/fs/cgroup cgroup defaults 0 0” >> /etc/fstab
1 | [root@localhost ~]# yum install -y libcgroup |
上述一步步比较多,可以整合在一起搞一个shell进行统一处理,因此有了下面的一键安装脚本
笔者希望在别的机器上能一键处理,自己写了个一键安装内核及docker的脚本。这里仅供给大家参考,但由于笔者水平有限,不可能写出非常完美的安装脚本,并不保证在每台机器上都能完美运行。也希望大家多多提建议,我可以改进这个脚本。
curl也可以用来下载如curl -O http://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-17.06.2-ce.tgz
1 | !/bin/sh |
执行完记得要重启机器
为什么有安装方式二?这个说来话长,公司内部有几台服务器无法连接外网,只能先下载好安装包。这个我就写简单点了哈。
| 安装包名 | 下载地址 |
| ———————— | ———————————————————— |
| linux4.4.132-1内核安装包 | http://elrepo.org/linux/kernel/el6/x86_64/RPMS/kernel-lt-4.4.132-1.el6.elrepo.x86_64.rpm |
| cgroup安装包 | http://mirror.centos.org/centos/6/updates/x86_64/Packages/libcgroup-0.40.rc1-24.el6_9.x86_64.rpm |
| docker安装包 | http://mirrors.ustc.edu.cn/docker-ce/linux/static/stable/x86_64/docker-17.06.2-ce.tgz |
1 | rpm -ivh kernel-lt-4.4.132-1.el6.elrepo.x86_64.rpm |
1 | echo "none /sys/fs/cgroup cgroup defaults 0 0" >> /etc/fstab |
vi /etc/grub.conf(这里有坑,可能会用/boot/grub/grub.conf)
得确定下是否存在 /boot/grub/grub.conf如果存在那么 grub启动用的是这个文件如果不存在用的是/etc/grub.conf安装完内核后,默认会在/etc/grub.conf下添加一条记录把/etc/grub.conf下的添加的新内核的启动记录复制到/boot/grub/grub.conf文件末尾,然后把/boot/grub/grub.conf的 default=0改为default=1
1 | tar -xzvf docker-17.06.2-ce.tgz && cp docker/* /usr/bin && rm -rf docker |
1 | mkdir -p /etc/docker |
1 | echo "nohup dockerd > /var/log/dockerd.log 2>&1 &" >> /etc/rc.local |
1 | reboot |
vi /etc/sysctl.conf
1 | # Controls IP packet forwarding |
echo “CentOS release 6.x (Final)” | tee /usr/lib/os-release
1 | [root@localhost ~]# echo "CentOS release 6.x (Final)" | tee /usr/lib/os-release |
报的错误是rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:262: starting container process caused “open /dev/ptmx: no such file or directory “,我看了下宿主机上是有这个文件,那么应该是容器里没有的原因,目前还没查到原因。不过可以通过启动容器时指定 -v /dev:/dev 映射宿主机/dev来解决
此问题可以在启动容器的时候时候,通过-v /dev:/dev来解决,如:
1 | docker run -it -d --name mynginx -p 80:80 -v /dev:/dev nginx:alpine |
[root@centos ~]# docker run -it alpine:3.7 /bin/sh
Unable to find image ‘alpine:3.7’ locally
3.7: Pulling from library/alpine
ff3a5c916c92: Pull complete
Digest: sha256:e1871801d30885a610511c867de0d6baca7ed4e6a2573d506bbec7fd3b03873f
Status: Downloaded newer image for alpine:3.7
docker: Error response from daemon: mkdir /var/lib/docker/overlay2/3520bc6c5dcc7ba4dc5e2ba1419cd6250c4b6d61797da7487f20d991179ddd05-init/merged/dev/shm: permission denied.
See ‘docker run –help’.
那么请yum update下,注意/etc/grub.conf的内核启动顺序,要改下
可以方便的实现 service dockerd start,service dockerd stop,service dockerd restart
创建一个dockerd文件,赋予可执行权限,并将该文件放到/etc/init.d即可
1 | !/bin/sh |
踩了好多坑,还是一点点解决了,有点抓狂,有点惊喜;还是得多多总结,多多把遇到的坑,怎么解决的经历整理成文,分享出来,以免更多人踩同样的坑。希望此文对有需要在CentOS6.6或CentOS6.x上安装docker的读者有一些帮助。
自己了解一些Docker容器技术,想在自己机器上搭建一个zookeeper集群,以前在虚拟机上有过实践经验,这次就想着在Docker下也搞一个Zookeeper集群。Docker的相关介绍这里就不多讲了,大家可以通过搜索引擎查阅相关资料。
Apache ZooKeeper是Apache的一个软件项目,它提供了一个开放源码的分布式配置服务、同步服务和大型分布式系统的命名注册服务。ZooKeeper是Hadoop的一个子项目,但目前是它本身就是一个顶级项目。
http://archive.apache.org/dist/zookeeper/zookeeper-3.4.11/zookeeper-3.4.11.tar.gz
docker pull zookeeper
到写文章时,pull下来zookeeper的版本也是3.4.11,后续pull下来可能不是3.4.11了,需注意,后续新版本有配置变更也请留意
Zookeeper集群部署并非一定需要奇数台服务器,任意台Zookeeper服务器都能部署且能够正常运行。
一个Zookeeper集群如果要对外提供可用的服务,那么集群中必须要有过半的机器正常工作并且彼此之间能够正常通信。基于这个特性,如果想搭建一个能够允许N台机器挂掉的集群,那么就需要部署一个由2XN+1台服务器构成的Zookeeper集群。因此,一个由5台机器构成的Zookeeper集群,能够在挂掉2台机器后依然正常工作,而如果是一个由6台服务器构成的Zookeeper集群,同样只能够挂掉2台机器,因为如果挂掉3台,剩下的机器就无法实现过半了。
因此,从上面的讲解中,我们可以看出,对于一个由6台服务器组成的Zookeeper集群来说,和一个由5台服务器构成的Zookeeper集群相比,其在容灾能力上没有任何明显的提升。基于这个原因,Zookeeper集群通常设计部署成奇数台服务器即可。
容灾主要是两个个方面,一个是单点问题,Zookeeper的单点问题可以通过搭建集群来解决。二是Zookeeper需要保证集群内服务器过半存活问题,可以考虑三机房部署(N1=N总/3,N2=N总/3, N3=N总-N1-N2,其中N代表机器数量)。对于双机房部署,可以将过半的Zookeeper部署在主机房。
注意跟系统属性配置方式区分开来,这个启动容器时可制定的环境变量与系统属性配置没有关系
启动容器时可指定的环境变量名 | 默认值 |
---|---|
ZOO_TICK_TIME | 2000 |
ZOO_INIT_LIMIT | 5 |
ZOO_SYNC_LIMIT | 2 |
ZOO_MAX_CLIENT_CNXNS | 60 |
ZOO_STANDALONE_ENABLED | false |
ZOO_MY_ID | 如果是在复制模式下该参数是必须的,要么在启动容器时,指定此环境变量。要么在数据目录的myid文件里指定 |
ZOO_SERVERS | 如果是在复制模式下该参数是必须的,要么在启动容器时,指定此环境变量。要么在zoo.cfg配置文件里指定 。This variable allows you to specify a list of machines of the Zookeeper ensemble. Each entry has the form of server.id=host:port:port . Entries are separated with space. Do note that this variable will not have any effect if you start the container with a /conf directory that already contains the zoo.cfg file. |
zookeeper官方镜像,数据目录是/data,日志目录是/datalog,配置目录是/conf。这里官方文档里给出了解释,如果你映射了配置目录/conf
如果没有映射配置文件目录或映射配置文件也没关系,zookeeper官方镜像提供了以下启动容器时的环境变量参数
启动一个zookeeper容器
1 | docker run --name some-zookeeper --restart always -d zookeeper |
启动一个应用的容器,这个应用是需要用到zookeeper的,可以通过–link some-zookeeper:zookeeper连接到上一步启动的zookeeper容器。some-zookeeper冒号后的zookeeper是别名
1 | docker run --name some-app --link some-zookeeper:zookeeper -d application-that-uses-zookeeper |
通过命令行客户端连接zookeeper,由于这里加了–rm参数,使得退出容器,容器马上会被删掉
1 | docker run -it --rm --link some-zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper |
stack.yml定义如下:
1 | version: '3.1' |
端口2181、2182、2183将会暴露出来
1 | docker-compose -f stack.yml up |
这种方式是一种伪集群方式,只适合在开发、测试环境中部署;生产环境是强烈不建议的,生产环境建议每个zookeeper实例部署在不同的物理机上。如果zookeeper实例部署在在同一物理机上的不同虚拟机,仍然不是严格意义的完全冗余。
通过表格形式来规划下各个zookeeper实例所需的myid,端口号,以及需要在宿主机上的对应的配置文件
容器名称 | ip | myid | 端口1 | 端口2 | 端口3 | 宿主机配置文件 | 宿主机上的data目录 |
---|---|---|---|---|---|---|---|
zk1 | ip1 | 1 | 2181 | 2881 | 3881 | /opt/data/zk1/conf/zoo.cfg | /opt/data/zk1/data |
zk2 | ip2 | 2 | 2182 | 2882 | 3882 | /opt/data/zk2/conf/zoo.cfg | /opt/data/zk2/data |
zk3 | ip3 | 3 | 2183 | 2883 | 3883 | /opt/data/zk3/conf/zoo.cfg | /opt/data/zk3/data |
上面表格显示有点问题,还是提供一张图片清楚一些,那么看图吧
端口1用于对client端提供服务,端口2用于选举leader,端口3用于集群内通讯使用(Leader会监听此端口),zk实例数应为大于等于3的奇数,如3、5、7。。。不宜太多。由于我是在同一台机器上启动的Zookeeper容器,所以ip1,ip2,ip3都是一样的。在同一台机器上启的话,是伪集群。
1 | mkdir -p /opt/data/zk1/data |
我实验的时候,是在同一台机器上操作的,如果是在不同的机器上,请分别在不同机器上创建目录
分别在/opt/data/zk1/data,/opt/data/zk2/data,/opt/data/zk3/data目录下创建myid文件,并写入相应的id
name | myid file | server id |
---|---|---|
zk1 | /opt/data/zk1/data/myid | 1 |
zk2 | /opt/data/zk2/data/myid | 2 |
zk3 | /opt/data/zk3/data/myid | 3 |
1 | cd /opt/data/zk1/data/ && echo 1 |tee myid |
以下配置只有clientPort是不同的,因为我是在同一台机器上跑的容器,端口一样会有冲突;如果在不同机器上跑,那clientPort可以是一样的。生产环境建议是在不同的机器上跑。分别修改/opt/data/zk1/conf/zoo.cfg,/opt/data/zk2/conf/zoo.cfg,/opt/data/zk3/conf/zoo.cfg
1 |
|
1 | # The number of milliseconds of each tick |
1 | # The number of milliseconds of each tick |
接下来就可以启动容器了,我是在同一台机器上启动的,用的docker的host网络。生产环境建议在不同的机器上启动zookeeper容器。
1 | docker run --name zk1 --net host --restart always -d -v/opt/data/zk1/data:/data -v /opt/data/zk1/conf/zoo.cfg:/conf/zoo.cfg -p 2181:2181 -p 2881:2881 -p 3881:3881 zookeeper |
以下有些默认值在新版本中可能已经改成其他值了,这个是需要注意的。这里还未去验证确切的默认值
配置项 | 解释 |
---|---|
clientPort | 该参数无默认值,必须配置,不支持系统属性方式配置。参数clientPort用于配置当前服务器对外的服务器端口,客户端会通过该端口和Zookeeper服务器创建连接,一般设置为2181。每台Zookeeper服务器都可以配置任意可用的端口,同时,集群中的所有服务器不需要保持clientPort端口一致。 |
dataDir | 该参数无默认值,必须配置,不支持系统属性方式配置。参数dataDir用于配置Zookeeper服务器存储快照文件的目录。默认情况下,如果没有配置参数dataLogDir,那么事务日志也会存储在这个目录中。考虑到事务日志的写性能直接影响Zookeeper整体的服务能力,因此建议同时通过参数dataLogDir来配置Zookeeper事务日志的存储目录。 |
tickTime | 该参数有默认值:3000,单位是毫秒(ms),可以不配置,不支持系统属性方式配置。参数tickTime用于配置Zookeeper中最小事件单元的长度,很多运行时的时间间隔都是使用tickTime的倍数来表示的。例如,Zookeeper中会话的最小超时时间默认是2*tickTime. |
系统属性方式配置:即在Java中,可以通过在启动的命令行参数中添加-D参数来达到配置系统属性的目的
例如 -Djava.library.path=/home/admin/jdk/lib, 就是通过系统属性来配置java.library.path的。
配置项 | 解释 |
---|---|
dataLogDir | 该参数有默认值:dataDir,可以不配置,不支持系统属性方式配置。参数dataLogDir用于配置Zookeeper服务器存储事务日志文件的目录。默认情况下,Zookeeper会将事务日志文件和快照数据存储在同一个目录中,使用者应尽量将这两者的目录区分开来。另外,如果条件允许,可以将事务日志的存储配置在一个单独的磁盘上。事务日志记录对于磁盘的性能要求非常高,为了保证数据的一致性,Zookeeper在返回客户端事务请求响应之前,必须将本次请求对应的事务日志写入到磁盘中。因此事务日志写入的性能直接决定了Zookeeper在处理事务请求时的吞吐。针对同一块磁盘的其他并发读写操作(例如Zookeeper运行时日志输出和操作系统自身的读写等),尤其是数据快照操作,会极大的影响事务日志的写性能。因此尽量给事务日志的输出配置一个单独的磁盘或是挂载点,将极大地提升Zookeeper的整体性能 |
initLimit | 该参数有默认值:10,即表示是参数tickTime值的10倍,必须配置,且需要配置一个正整数,不支持系统属性方式配置。该参数用于配置Leader服务器等待Follower启动,并完成数据同步的时间。Follower服务器在启动过程中,会与Leader建立连接并完成对数据的同步,从而确定自己对外提供服务的起始状态。通常情况下,运维人员不用太在意这个参数的配置,使用期默认值即可。但如果随着Zookeeper集群管理的数据量增大,Follower服务器在启动的时候,从Leader上进行同步数据的时间也会相应变长,于是无法在较短的时间完成数据同步。因此,在这种情况下,有必要适当调大这个参数。 |
syncLimit | 该参数有默认值:5,即表示是参数tickTime值的5倍,必须配置,且需要配置一个正整数,不支持系统属性当时配置。该参数用于配置Leader服务器和Follower之间进行心跳检测的最大延时事件。在Zookeeper集群运行过程中,Leader服务器会与所有的Follower进行心跳检测来确定该服务器是否存活。如果Leader服务器在syncLimit时间内无法获取到Follower的心跳检测响应,那么Leader就会认为该Follower已经脱离了和自己的同步。通常情况下,运维人员使用该参数的默认值即可,但如果部署Zookeeper集群的网络环境质量较低(例如网络延时较大或丢包严重),那么可以适当调大这个参数。 |
snapCount | 该参数有默认值:100000,可以不配置,仅支持系统属性方式配置:zookeeper.snapCount。参数snapCount用于配置相邻两次数据快照之间的事务操作次数,即Zookeeper会在snapCount次事务操作之后进行一次数据快照。 |
preAllocSize | 该参数有默认值:65536,单位是KB,即64MB,可以不配置,仅支持系统属性方式配置:zookeeper.preAllocSize。参数preAlloSize用于配置Zookeeper事务日志文件预分配的磁盘空间大小。通常情况下,我们使用Zookeeper的默认配置65536KB即可,但是如果我们将参数snapCount设置得比默认值更小或更大,那么preAllocSize参数也要随之做出变更。举例来说:如果我们将snapCount的值设置为500,同时预估每次事务操作的数据量大小至多1KB,那么参数preAllocSize设置为500就足够了。 |
minSessionTimeout/maxSessionTimeout | 这两个参数有默认值,分别是参数tickTime值的2倍和20倍,即默认的会话超时时间在2tickTime~20tickTime范围内,单位毫秒,可以不配置,不支持系统属性方式配置。这两个参数用于服务端对客户端会话的超时时间进行限制,如果客户端设置的超时时间不在该范围内,那么会被服务端强制设置为最大或最小超时时间。 |
maxClientCnxns | 该参数有默认值:60,可以不配置,不支持系统属性方式配置。从Socket层面限制单个客户端与单台服务器之间的并发连接数,即以IP地址粒度来进行连接数的限制。如果将该参数设置为0,则表示对连接数不做任何限制。需要注意该连接数限制选项的适用范围,其仅仅是对单台客户端机器与单台Zookeeper服务器之间的连接数限制,并不能控制所有客户端的连接数总和。另外,在3.4.0版本以前该参数的默认值都是10,从3.4.0版本开始变成了60,因此运维人员尤其需要注意这个变化,以防Zookeeper版本变化带来服务器连接数限制变化的隐患。 |
jute.maxbuffer | 该参数有默认值:1048575,单位是字节,可以不配置,仅支持系统属性方式配置:jute.maxbuffer。该参数用于配置单个数据节点(ZNode)上可以存储的最大数据量大小。通常情况下,运维人员不需要改动该参数,同时考虑到Zookeeper上不适宜存储太多的数据,往往还需要将该参数设置得更小。需要注意的是,在变更该参数的时候,需要在Zookeeper集群的所有机器以及所有的客户端上均设置才能生效。 |
clientPortAddress | 该参数没有默认值:可以不配置,不支持系统属性方式配置。针对那些多网卡的机器,该参数允许为每个IP地址制定不同的监听端口。 |
server.id=host:port:port | 该参数没有默认值,在单机模式下可以不配置,不支持系统属性方式配置。该参数用于配置组成Zookeeper集群的及其列表,其中id即为ServerID,与每台服务器myid文件中的数字相对应。同时,在该参数中,会配置两个端口:第一个端口用于指定Follower服务器与Leader进行运行时通信和数据同步时所使用的端口,第二个端口则专门用于进行Leader选举过程中的投票通信。在Zookeeper服务器启动的时候,其会根据myid文件中配置的ServerID来确定自己是哪台服务器,并使用对应配置的端口来进行启动。如果在实际使用过程中,需要在同一台服务器上部署多个Zookeeper实例来构成伪集群的话,那么这些端口都需要配置成不同。例如 server.1=192.168.0.1:2777:3777 , server.1=192.168.0.1:2888:3888 , server.1=192.168.0.1:2999:3999 不过这个参数在3.5.0版本开始,有所改变了,具体查看zookeeper官方文档 |
autopurge.snapRetainCount | 该参数有默认值:3,可以不配置,不支持系统属性方式配置。从3.4.0版本开始,Zookeeper提供了对历史事务日志和快照数据自动清理的支持。参数autopurge.snapRetainCount用于配置Zookeeper在自动清理的时候需要保留的快照数据文件数量和对应的事务日志万能键。需要注意的是,并不是磁盘上的所有事务日志和快照数据文件都可以被清理掉–那样的话将无法恢复数据。因此参数autopurge.snapRetainCount的最小值是3,如果配置的autopurge.snapRetainCount值小于3的话,那么会被自动调整到3,即至少需要保留3个快照数据文件和对应的事务日志文件 |
autopurge.purgeInteval | 该参数有默认值:0,单位是小时,可以不配置,不支持系统属性方式配置。参数autopurge.purgeInterval和参数autopurge.snapRetainCount配套使用,用于配置Zookeeper进行历史文件自动清理的频率。如果配置该值为0或者负数,那么就表明不需要开启定时清理功能。Zookeeper默认不开启这项功能。 |
fsync.warningthresholdms | 该参数有默认值:1000,单位是毫秒,可以不配置,仅支持系统属性方式配置:fsync.warningthresholdms。参数fsync.warningthresholdms用于配置Zookeeper进行事务日志fsync操作时消耗时间的报警阈值。一旦进行一个fsync操作消耗的事件大于参数fsync.warningthresholdms指定的值,那么就在日志中打印出报警日志。 |
forceSync | 该参数有默认值:yes,可以不配置,可选配置项为yes 和no ,仅支持系统属性方式配置:zookeeper.forceSync。该参数用于配置Zookeeper服务器是否在事务提交的时候,将日志写入操作强制刷入磁盘(即调用java.nio.channels.FileChannel.force接口),默认情况下是yes ,即每次事务日志写入操作都会实时刷入磁盘。如果将其设置为no 。则能一定程度的提高Zookeeper的写性能,但同时也会存在类似于机器断电这样的安全风险。 |
globalOutstandingLimit | 该参数有默认值:1000,可以不配置,仅支持系统属性方式配置:zookeeper.globalOutstandingLimit。参数globalOutstandingLimit用于配置Zookeeper服务器最大请求堆积数量。在Zookeeper服务器运行的过程中,客户端会源源不断的将请求发送到服务端,为了防止服务端资源(包括CPU,内存和网络等)耗尽,服务端必须限制同时处理的请求数,即最大请求堆积数量。 |
leaderServes | 该参数有默认值:yes,可以不配置,可选配置项为yes 和no ,仅支持系统属性方式配置:zookeeper.leaderServes。该参数用于配置Leader服务器是否能够接受客户端的连接,即是否允许Leader向客户端提供服务,默认情况下,Leader服务器能够接受并处理客户端的所有读写请求。在Zookeeper的架构设计中,Leader服务器主要用来进行对事务更新请求的协调以及集群本身的运行时协调,因此,可以设置让Leader服务器不接受客户端的连接,以使其专注于进行分布式协调。 |
skipAcl | 该参数有默认值:no,可以不配置,可选配置项为yes 和no ,仅支持系统属性方式配置:zookeeper.skipACL。该参数用于配置Zookeeper服务器是否跳过ACL权限检查,默认情况下是no ,即会对每一个客户端请求进行权限检查。如果将其设置为yes ,则能一定程度的提高Zookeeper的读写性能,但同时也会向所有客户端开放Zookeeper的数据,包括那些之前设置过ACL权限的数据节点,也将不再接收权限控制。 |
xncTimeout | 该参数有默认值:5000,单位是毫秒,可以不配置,仅支持系统属性方式配置:zookeeper.cnxTimeout。该参数用于配置在Leader选举过程中,各服务器之间进行TCP连接创建的超时时间。 |
electionAlg | 在之前的版本中,可以使用该参数来配置选择Zookeeper进行Leader选举时所使用的算法,但从3.4.0版本开始,Zookeeper废弃了其他选举算法,只留下了FastLeaderElection算法,因此该参数目前看来没有用了。 |
https://hub.docker.com/_/zookeeper/
http://zookeeper.apache.org/doc/r3.4.11/zookeeperAdmin.html
https://www.hifreud.com/2017/01/09/zookeeper-06-common-configurations/
http://www.cnblogs.com/sunddenly/articles/4071730.html
]]>线性表的链式存储结构的特点是用一组任务的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是非连续的。这就意味着,这些数据元素可以存储在内存未被占用的任意位置。
线性表的顺序存储结构中,每个数据元素只需要存储数据元素信息就可以了。现在链式结构中,除了要存储数据元素外,还要存储它的后继元素的存储地址。
为了表示每个数据元素ai与其直接后继元素的ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其后继的信息(即直接后继的存储位置)。我们把存储数据元素的域称为数据域,把存储直接后继位置的域成为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,成为结点(Node)。
n个结点链结成一个链表,即为线性表(a1,a2,…ai,ai+1,…,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
1 | typedef int ElementType; |
data为数据域,next为指针域
插入操作算法描述
1 | Status listInsert(LinkedList linkedList, int i, ElementType e) { |
删除操作算法描述
1 | Status listDelete(LinkedList linkedList, int i, ElementType *e) { |
反转算法描述
设置3个指针,cur指向当前要操作的元素(操作当前元素的指针域),pre指向前驱元素,next指向后继元素,next用于保存暂存后继元素的位置信息
1 | LinkedList listReverse(LinkedList list) { |
算法描述
1 | Status listSearch(LinkedList list, int i, ElementType *e) { |
算法描述
1 | Node* listTheBackKthNode(LinkedList list, int k) { |
算法描述
与上述查找倒数第k个结点的算法一样,只不过倒数第几个,要根据链表长度计算一下而已,不再赘述
1 | Node* listTheMiddleNode(LinkedList list) { |
1 |
|
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,…,ai,ai+1,…,an-1,an},每个元素的类型为DataType。其中,除第一个元素a1外,每个元素都有一个直接前驱元素,除最后一个元素an外,每个元素都有一个直接后继元素,数据元素之间的关系是一对一的
Operation:
initList(List *list): 初始化操作,建立一个空的线性表list
listEmpty(List list):若线性表为空,则返回true,否则返回false
clearList(List *list):将线性表清空
getElement(List list,int i,ElementType *e):获取线性表第i个元素的值,该值赋值给e
locateElement(List list,ElementType e):在线性表中查找与给定e值相等的元素,如果查找成功则返回元素在线性表中的序号,否则返回0
listInsert(List *list,int i,ElementType e):在线性表序号为i处,新建并插入值为e的新元素,插入成功返回TRUE,否则返回FALSE
listDelete(List list,int i,ElementType e):删除线性表中序号为i的元素,并用e来存储元素值返回给调用方,如果删除成功返回TRUE,否则返回FALSE
注:这个定义对于线性表的顺序存储与链式存储都是适用的
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
若存在线性表{a1,a2,…,ai,ai+1,…,an-1,an},LOC表示求地址操作,每个数据元素需要m存储空间,则有LOC(ai+1)=LOC(ai)+m, LOC(ai)=LOC(a1)+(i-1)*m
1 |
|
这里,我们就发现线性表顺序存储结构的三个特点
插入操作算法描述
注意:这里第i个元素,在C语言数组的序号表示为i-1
1 | Status listInsert(SqList *sqList, int i, ElementType e) { |
插入操作时间复杂度为O(n)
删除操作算法描述
1 | Status listDelete(SqList *sqList, int i, ElementType *e) { |
删除操作时间复杂度为O(n)
1 |
|
优点
缺点
之前【Spring Cloud微服务全家桶】之高可用服务注册中心 文章介绍了Eureka Server高可用
观察eureka-peer1,发现unavailable-replicas与available-replicas显示信息不正确,按理说所有eureka节点都启动后,eureka-peer3,eureka-peer2应该出现在available-replicas列表
修改下每个eureka节点的配置,把自身注册到注册中心
1 | eureka.client.fetch-registry=true |
1 | server.port=1011 |
1 | server.port=1012 |
1 | server.port=1013 |
重启每个eureka节点
发现available-replicas栏可以出现了
在Spring Cloud微服务全家桶之服务注册、服务发现(Eureka、Consul作为服务注册中心)文中,使用的是单点eureka注册中心。在开发测试环境是可以的,但生产环境强烈不建议用单点eureka注册中心。
思路就是每个eureka server通过eureka.client.serviceUrl.defaultZone配置,将自己注册到对方的注册中心。
新建一个module项目,建的过程这里省略,名为registry-ha-ms,建完后pom.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
使用@EnableEurekaServer注解启用Eureka注册中心
1 |
|
建两个properties配置文件,分别为application-peer1.properties,application-peer2.properties
1 | server.port=1011 |
1 | server.port=1012 |
配置本地Hosts域名解析
1 | 127.0.0.1 eureka-peer1 |
在项目目录下使用maven clean package -DskipTests打包,进入target目录,开启两个命令行窗口分别执行
1 | java -jar registry-ha-ms-0.0.1-SNAPSHOT --spring.profiles.active=peer1 |
或者在idea里建两个执行配置
这样就开启了两个eureka server。
我们在上一篇文章的基础上,将provider-ms的application.properties修改下
1 | server.port=2103 |
然后再启动ProviderApplication,可以看到PROVIDER-MS 已经注册到eureka-peer1与eureka-peer2上了。且eureka-peer1的replicas节点列表里有eureka-peer2,eureka-peer2的replicas节点列表里有eureka-peer1
新建一个服务消费项目,名为consumer-ms建完后pom.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
修改properties资源文件
1 | server.port=2104 |
@EnableDiscoveryClient启用服务发现客户端
1 |
|
写一个Controller消费provider-ms提供的服务
1 |
|
这里引入了LoadBalancerClient,通过choose方法负载均衡选择一个“provider-ms”的服务实例,再通过restTemplate调用,服务实例的信息存储在ServiceInstance对象中,可以获取到主机名,端口号等信
启动ConsumerMsApplication,观察eureka-peer1,eureka-peer2,consumer-ms已经注册了
此时调用刚才写的consume接口,发现已经能够将注册中心的两个服务实例展示出来了,说明服务间调用成功了
虽然上面我们以双节点作为例子,但是实际上因负载等原因,我们往往可能需要在生产环境构建多于两个的Eureka Server节点。那么对于如何配置serviceUrl来让集群中的服务进行同步,需要我们更深入的理解节点间的同步机制来做出决策。Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。什么意思呢?不妨我们通过下面的实验来看看会发生什么。场景一:假设我们有3个注册中心,我们将eureka-peer1、eureka-peer2、eureka-peer3各自都将eureka.client.serviceUrl.defaultZone指向另外两个节点。换言之,peer1、peer2、peer3是两两互相注册的。启动三个服务注册中心,并将provider-ms的serviceUrl指向eureka-peer1并启动,可以获得如下图所示的集群效果。
我们在上述双eureka节点高可用的基础上,增加一个application-peer3.properties,并修改3个properties配置文件,内容如下:
1 | application-peer1.properties |
application-peer2.properties
1 | server.port=1012spring.application.name=registry-ha-ms |
application-peer3.properties
1 | server.port=1013spring.application.name=registry-ha-ms |
复制一个,改名RegistryHaApplication-peer3 ,其中Program arguments: –spring.profiles.active=peer3
配置本地Hosts域名解析
1 | 127.0.0.1 eureka-peer1127.0.0.1 eureka-peer2127.0.0.1 eureka-peer3 |
分别启动RegistryHaApplication-peer1,RegistryHaApplication-peer2,RegistryHaApplication-peer3
观察eureka-peer1,eureka-peer2,eureka-peer3,每个eureka都已经两两互相注册,且目前没有服务实例注册在注册中心(Instances currently registered with Eureka,No instance available)
试验下单边注册一个服务实例,即provider-ms的serviceUrl指向eureka-peer1修改provider-ms项目的application.properties配置,仅仅配置将provider-ms注册到eureka-peer1,然后启动ProviderApplication
server.port=2103spring.application.name=provider-ms#eureka.client.serviceUrl.defaultZone=http://registry-ms.pay-inner.com:1001/eurekaeureka.client.serviceUrl.defaultZone=http://eureka-peer1:1011/eureka
观察eureka-peer1,eureka-peer2,eureka-peer3发现provider-ms服务,已经通过eureka-peer1将服务实例信息同步给了eureka-peer2,eureka-peer3
进一步,我们再将consumer-ms进行eureka-peer2单边注册
观察eureka-peer1,eureka-peer2,eureka-peer3,此时eureka-peer2同步consumer-ms的服务实例信息到eureka-perr1,eureka-peer3
然后我们调用下consumer-ms的consume接口,没有问题
将eureka-peer1 shutdownprovider-ms开始报错,访问不到eureka-peer1
但这并不影响,consumer-ms调用provider-ms
将eureka-peer2 shutdownconsumer-ms开始报错,访问不到eureka-peer2
但丝毫不影响,consumer-ms调用provider-ms,做到了服务注册中心的高可用!(这里有点不严谨,因为即使把所有eureka节点都shutdown,还是可以调用的,服务消费方都会本地缓存服务提供方的地址的,但是如果eureka节点全部shutdown,那么新加入的服务实例就无法感知了)
不过,不建议每个服务配置单边注册到某个eureka节点,强烈建议,每个服务配置所有eureka节点即provider-ms,consumer-ms的application.properties中的eureka.client.serviceUrl.defaultZone配成所有eureka节点
1 | eureka.client.serviceUrl.defaultZone=http://eureka-peer1:1011/eureka,http://eureka-peer2:1012/eureka,http://eureka-peer3:1013/eureka |
这个配置的试验就不再赘述,可以自行尝试
]]>