shengyayun 10 X 10
shengyayun:~$ ssh shengyayun@tech
Welcome to shengyayun's Notebook.

shengyayun:~/blog$ cat ./tags
collectd centos Seata docker grafana kubernetes influxdb java maven manifest mongodb mysql nacos nginx php prometheus raft ssh swoole voltdb

shengyayun:~/blog$ ls post/part1
2022-02-25 分布式事务:TCC模式
2022-02-25 分布式事务:AT模式
2021-09-05 Prometheus
2021-03-27 SSH的常用指令
2020-05-02 部署一个单机模式的Nacos
2019-07-25 mysql表占用空间分析
2019-05-13 部署Docker容器到Kubernetes
2019-05-04 创建Docker私有仓库
2019-04-29 通过Dockerfile生成镜像
2019-04-22 Raft算法分析

shengyayun:~/blog$ curl sites.list
Google

分布式事务:TCC模式

场景

  1. 用户提交订单;
  2. 减少库存数量;

术语

Try:资源的检测和预留;
Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
Cancel:预留资源释放;
幂等性:Confirm与Cancel必须实现幂等,因为如果执行失败,TC会不断重试。
允许空回滚:Try未执行过的情况下,也可以Cancel成功。
防悬挂控制:Cancel后Try被调用也不执行。

时序图

分布式事务:AT模式

场景

  1. 用户提交订单;
  2. 减少库存数量;

术语

TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

时序图

Prometheus

Prometheusis an open-source systems monitoring and alerting toolkit originally built at SoundCloud.
普罗米修斯是最初由SoundCloud建立的一套开源的系统监控及预警的工具。

[TOC]

一. 软件简介

Prometheus 与 Influxdb都是时序数据库,比起关系型数据库,它们在时间序列数据的处理上具有极大的优势,例如长期高频率记录温度传感器的数值,该数据跟时间关联较大,且数据量极大。

二. 安装 Prometheus

1. 下载

1
2
3
4
5
6
7
8
# /opt 目录类似于Windows的C://Program Files/目录
cd /opt/
# 下载prometheus的压缩文件
wget https://github.com/prometheus/prometheus/releases/download/v2.29.1/prometheus-2.29.1.linux-amd64.tar.gz
# 解压
tar xzvf prometheus-2.29.1.linux-amd64.tar.gz
# 创建软链接,便于后续进行版本升级
ln -snf /opt/prometheus-2.29.1.linux-amd64.tar.gz /opt/prometheus

2. Systemd

创建Service文件vi /usr/lib/systemd/system/prometheus.service

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Prometheus
After=network.target # 依赖于网络服务

[Service]
Type=simple # 启动类型:ExecStart 创建的进程作为主进程
User=nobody # 执行用户使用 nobody
ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/mnt/var/prometheus --web.console.libraries=/opt/prometheus/console_libraries --web.console.templates=/opt/prometheus/consoles --web.enable-lifecycle # 启动 prometheus 进程
Restart=on-failure # 失败自动重启

[Install]
WantedBy=multi-user.target # 可以通过 systemctl enable 启用,将会将该服务包含到 multi-user.target 中,这样在启动 multi-user.target 时,将会自动启动 multi-user.target

使用Systemd的Service的好处在于,可以开机自启动,并且失败可以自动重启,简化了运维管理。

重新加载Service文件:systemctl daemon-reload
开机自启动:systemctl enable prometheus
启动服务:systemctl start prometheus
查看服务状态:systemctl status prometheus
查看服务输出:journalctl -xe -u prometheus

3. 自带的图形化界面

访问 http://127.0.0.1:9090/graph

三. 安装 Exporter

Prometheus 的官方与第三方提供了多种 Exporter,它们会采集各种监控数据,供Prometheus定期拉取(Pull)。

1. Node Exporter(硬件与操作系统)

1
wget https://github.com/prometheus/node_exporter/releases/download/v1.2.2/node_exporter-1.2.2.linux-amd64.tar.gz

vi /usr/lib/systemd/system/node_exporter.service

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=NodeExporter
After=network.target

[Service]
Type=simple
User=nobody
ExecStart=/opt/node_exporter/node_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target

systemctl daemon-reload & systemctl enable node_exporter & systemctl start node_exporter

2. Mysqld Exporter(MySQL服务)

1
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.13.0/mysqld_exporter-0.13.0.linux-amd64.tar.gz

vi /usr/lib/systemd/system/mysqld_exporter.service

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=MysqldExporter
After=network.target

[Service]
Type=simple
User=nobody
Environment=DATA_SOURCE_NAME=user:password@(hostname:3306)/ # TODO Change
ExecStart=/opt/mysqld_exporter/mysqld_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target

systemctl daemon-reload & systemctl enable mysqld_exporter & systemctl start mysqld_exporter

3. Kafka Exporter

1
wget https://github.com/danielqsj/kafka_exporter/releases/download/v1.3.1/kafka_exporter-1.3.1.linux-amd64.tar.gz

vi /usr/lib/systemd/system/kafka_exporter.service

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=KafkaExporter
After=network.target

[Service]
Type=simple
User=nobody
ExecStart=/opt/mysqld_exporter/kafka_exporter
Restart=on-failure

[Install]
WantedBy=multi-user.target

systemctl daemon-reload & systemctl enable kafka_exporter & systemctl start kafka_exporter

四. Pull

Prometheus 根据配置文件中的scrape_configs,自动定期拉从Exporter拉取数据。

1. 修改配置文件

vi /opt/prometheus/prometheus.yml

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
# 全局配置
global:
scrape_interval: 15s # 每隔15s从Exporter拉取一次数据(根据scrape_configs)
evaluation_interval: 15s # 每隔15s计算一次规则(根据rule_files)

# Alertmanager相关配置
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093

# 规则文件列表
rule_files:

# 抓取配置列表
scrape_configs:
- job_name: "prometheus" # 自带的Job,拉取Prometheus自身的数据
static_configs:
- targets: ["localhost:9090"]

## 以下为新增项

- job_name: "nodes" # 拉取 Node Exporter 的数据
static_configs:
- targets: ["localhost:9100"] # Node Exporter 默认监听 9100 端口

- job_name: "mysqld" # 拉取 Mysqld Exporter 的数据
static_configs:
- targets: ["localhost:9104"] # Node Exporter 默认监听 9104 端口

- job_name: "kafka" # 拉取 Kafka Exporter 的数据
static_configs:
- targets: ["localhost:9308"] # Node Exporter 默认监听 9308 端口

- job_name: "pushgateway"
static_configs:
- targets: ["localhost:9091"] # PushGateway 默认监听 9091 端口

2. 要求Prometheus重新加载配置文件

1
2
# 如果未添加 -web.enable-lifecycle,这个接口会返回:Lifecycle API is not enabled
curl -X POST http://localhost:9090/-/reload

五. 更好的可视化工具:Grafana

1. 安装并启动

1
2
3
4
wget https://dl.grafana.com/oss/release/grafana-8.1.1-1.x86_64.rpm
yum install grafana-8.1.1-1.x86_64.rpm

systemctl start grafana-server

2. 初始管理员密码

访问 http://localhost:3000, 默认账户为 admin:admin, admin首次登陆需要重设密码。

3. 数据源与模板

添加Prometheus为数据源,然后去Grafana的Dashboards找一些合适的模板。

1 Node Exporter for Prometheus Dashboard CN v20201010

SSH的常用指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 生成密钥对
ssh-keygen -t rsa -P '' -m PEM

# 权限设置
chmod 700 .ssh
chmod 600 .ssh/authorized_keys

# 转换OpenSSH格式
puttygen id_rsa -o id_rsa.ppk
puttygen id_rsa.ppk -O private-openssh -o id_rsa2

# 排查异常登录的IP使用的公钥
cat /var/log/secure | grep 115.192.217.112
# Accepted publickey for root from 115.192.217.112 port 57305 ssh2: RSA SHA256:4egcXPHAIXpN7XxEiBQVV5dWEtQvi4JzbicrBJRZp1I
ssh-keygen -lf ~/.ssh/authorized_keys | grep 4egcXPHAIXpN7XxEiBQVV5dWEtQvi4JzbicrBJRZp1I
部署一个单机模式的Nacos

部署于Centos7系统,需要额外安装MySQL、JDK8+。

一. MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 下载Centos7使用的MySQL5.7
# 截止2020-05-03,Nacos最新版本不支持MySQL8+
wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
yum localinstall mysql57-community-release-el7-10.noarch.rpm
yum install mysql-community-server

# 启用MySQL服务
systemctl start mysqld

# 从mysql日志中查找root的一次性密码
grep password /var/log/mysqld.log
# 2020-05-02T02:37:53.274469Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: )jm8BYphsfa>
# 发现系统默认生成的root密码为 )jm8BYphsfa>

# 更新root密码,否则无法执行其他sql
mysql -uroot -p')jm8BYphsfa>'
alter user 'root'@'localhost' identified by '一个很复杂很安全的密码';

# 创建Nacos需要的数据库
create database nacos_db;

二. JDK8+

不要忘记设置PATH、JAVA_HOME

三. Nacos

查找Nacos的最新的稳定包: https://github.com/alibaba/nacos/releases

1
2
3
4
5
6
7
8
9
10
11
12
13
# 下载最新的文档包,这里是1.2.1
wget https://github.com/alibaba/nacos/releases/download/1.2.1/nacos-server-1.2.1.tar.gz
# 解压
tar xzvf nacos-server-1.2.1.tar.gz
# 将解压出的目录移动到/opt目录下(目录可自选)
mv nacos /opt/

# 编辑Nacos的配置文件,
vi /opt/nacos/conf/application.properties
## 涉及db.url.0、db.user、db.password。注意db.url.0中的数据库使用第一步中创建的nacos。

# 启用单机模式的Nacos
sh /opt/nacos/bin/startup.sh -m standalone

编辑Nacos的配置文件vi /opt/nacos/conf/application.properties

1
2
3
4
5
6
7
8
# 取消MySQL相关的注释
spring.datasource.platform=mysql
db.num=1
# 数据库使用步骤一中创建的数据库nacos_db
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_db?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
# 不推荐使用root,可自行创建一个仅有nacos库读写权限的账户
db.user=nacos_user
db.password=nacos_passwd

启动单机模式的Nacos:

1
sh /opt/nacos/bin/startup.sh -m standalone
mysql表占用空间分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT 
table_schema, #库名
table_name, #表名
table_comment, #表注释
`engine`, #表引擎
ROUND(data_length / 1024 / 1024, 2) AS 'Data', #数据占用(MB)
ROUND(data_free / 1024 / 1024, 2) AS 'Free', #碎片占用(MB)
ROUND(index_length / 1024 / 1024, 2) AS 'Index', #索引占用(MB)
ROUND((data_length + data_free + index_length) / 1024 / 1024, 2) AS 'Total' #合计占用
FROM
information_schema.tables
WHERE
table_schema NOT IN ('mysql' , 'sys', 'information_schema', 'performance_schema')
ORDER BY data_length + data_free + index_length DESC;
部署Docker容器到Kubernetes

Get Started, Part 5: Stacks
Compose file version 3 reference

导言

虽然docker默认的swarm与k8s(kubernetes)处于竞争关系,docker依然可以部署容器到k8s。

1
2
3
4
5
6
7
8
9
10
11
12
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
Kubernetes:
Version: v1.10.11
StackAPI: v1beta2

一. vi compose.yml

文件内容如下(保存前删除注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: "3.7"                                        #compose file的版本,Docker Engine版本18.06.0+支持3.7
services: #service中运行的单个容器被称为task,系统中的单个模块被称为service
web:
image: localhost:5000/hello #使用私有镜像localhost:5000/hello(这里必须使用仓库中的,不可以用本地的)
deploy: #部署操作
replicas: 5 #部署5个副本
resources:
limits:
cpus: "0.1" #每个副本最多使用10%的CPU时间片
memory: 50M #每个副本最多使用50MB的RAM
restart_policy:
condition: on-failure #容器运行失败后自动重启
ports:
- "4000:80" #本地的4000端口映射到容器的80端口
redis:
image: redis #docker hub上的redis镜像
ports:
- "6379:6379" #本地的6379端口映射到容器的6379端口
volumes:
- "/home/docker/data:/data" #容器的/data目录挂载到本地的/home/docker/data目录(redis会将日志文件存储到/data目录)
deploy:
placement:
constraints: [node.role == manager] #限制本service只在manager节点执行
command: redis-server --appendonly yes #启动redis-server,同时指定数据持久化策略:AOF模式

二. docker stack deploy

1
2
3
4
5
6
7
# service组成的完整的功能模块(甚至是系统)被称为stack。指定orchestrator为k8s,通过上一步保存的yml文件,生成名为hello的stack。
docker stack deploy --orchestrator=kubernetes -c compose.yml hello

# 查看k8s的全部resources
kubectl get all
# 查看docker的全部stack
docker stack ls
创建Docker私有仓库

Deploy a registry server
Use volumes
Use bind mounts
Garbage collection
HTTP API V2
Definition of: repository

导言

本文介绍如何在本地部署docker私有仓库,涉及到registry镜像(registry:2)、volume与bind(bind mount)、仓库的http api、仓库的垃圾回收等知识点。

1
2
3
4
5
6
7
8
9
10
11
12
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
Kubernetes:
Version: v1.10.11
StackAPI: v1beta2

一. 创建一个volume

1
2
3
4
5
#创建一个名为registry-vol的卷
docker volume create registry-vol

#执行以下指令,可以看到有一条VOLUME NAME为registry-vol的记录
docker volume ls

与bind直接使用宿主的文件系统不同,volume由docker直接生成与管理,它跨系统、跨平台、易于备份与迁移。

二. 创建私有仓库

1
2
3
4
5
6
7
8
9
10
11
#从docker hub拉取私有仓库的镜像
docker pull registry:2

#创建容器
# -d 在后台运行容器
# -p 5000:5000 将本地的5000(冒号左边)端口映射到容器的5000(冒号右边)端口
# --mount src=registry-vol,dst=/var/lib/registry 将名为registry-vol的volume映射到容器的/var/lib/registry目录(通过volume的方式进行数据持久化)
# --restart=always 容器自动重启
# -e REGISTRY_STORAGE_DELETE_ENABLED=true 设置环境变量,使得私有仓库中的镜像可以被删除。
# registry:2 image:tag
docker run -d -p 5000:5000 --mount type=volume,src=registry-vol,dst=/var/lib/registry --restart=always -e REGISTRY_STORAGE_DELETE_ENABLED=true registry:2

这里额外提供一个案例:

1
2
3
4
5
#创建容器
# --mount type=bind,src=/mnt/registry,dst=/var/lib/registry 将本地的/mnt/registry映射到容器的/var/lib/registry目录(通过bind mount的方式进行数据持久化)
# -v `pwd`/config.yml:/etc/docker/registry/config.yml 将本地当前目录的config.yml文件映射到容器的/etc/docker/registry/config.yml。
docker run -d -p 5000:5000 --mount type=bind,src=/mnt/registry,dst=/var/lib/registry --restart=always -v `pwd`/config.yml:/etc/docker/registry/config.yml registry:2

需要注意的是:

  1. 第一个案例中的-e REGISTRY_STORAGE_DELETE_ENABLED=true,在第二个案例中可以通过在配置文件中添加registry:storage:delete:enabled:true来替代。
  2. 虽然-v的功能和–mount相似,但推荐使用较为冗长的–mount,因为它简单易懂。

至此,私有仓库已经创建成功,我们可以通过docker提供的api确认一下:

1
2
3
#访问私有仓库的接口
curl http://localhost:5000/v2
#输出结果为{},说明私有仓库已经在正常运行

三. 推送镜像到私有仓库

1
2
3
4
5
6
7
8
#将本地镜像hello:latest(该镜像来自上个笔记)添加一个标签,其中localhost:5000对应私有仓库地址
docker tag hello localhost:5000/hello
#docker根据tag,将镜像提交到私有仓库localhost:5000
docker push localhost:5000/hello

#通过docker的http api查看存储的repository列表
curl http://localhost:5000/v2/_catalog
#输出结果为:{"repositories":["hello"]},说明已经提交成功。现在即使删除本地的镜像,也可以通过`docker pull localhost:5000/hello`从私有仓库重新获取。

repository指的是一组Docker镜像。 repository可以通过推送到仓库服务来分享。同一个repository中的不同镜像可以通过标签来归类。

hello镜像推送成功后,私有仓库会生成一个名为hello的repository。这个repository会存储各个tag的hello镜像,例如:hello:v1、hello:v2。

四. 搭建私有仓库的管理后台

对于私有仓库,docker只提供了http api的接口文档,它并未提供官方的管理后台。为了方便学习,采用第三方提供的Joxit/docker-registry-ui

1
2
3
4
5
6
7
8
#拉取镜像
docker pull joxit/docker-registry-ui:static
#创建容器
# -p 5050:80 将本地的5050端口映射到容器的80端口
# -e REGISTRY_URL=http://172.17.0.1:5000 通过环境变量设置容器访问的仓库地址为 http://172.17.0.1:5000
# -e DELETE_IMAGES=true 通过环境变量设置镜像可删除
# -e REGISTRY_TITLE="My registry" 通过环境变量设置后台的title
docker run -d -p 5050:80 -e REGISTRY_URL=http://172.17.0.1:5000 -e DELETE_IMAGES=true -e REGISTRY_TITLE="My registry" joxit/docker-registry-ui:static

这里的REGISTRY_UR并不是 http://localhost:5000 ,因为容器和本机的localhost并不等价。通过以下方式取得在对应的docker网络中本机的局域网地址:

1
2
3
#后台容器采用默认的bridge网络, 查询该网络的详细属性
docker network inspect bridge
#输出结果中发现IPAM.Config.Gateway为172.17.0.1,这就是本机在bridge网络中的局域网地址了。

通过浏览器访问 http://localhost:5050 ,可以通过管理后台对私有仓库进行管理了。

五. 删除私有仓库中的hello

通过后台页面,找到删除功能并不复杂。但是即使删除成功,后台的repository列表中依然存在hello(虽然再也无法拉取镜像)。这并不是管理后台的问题,下面通过接口确认:

1
2
3
#通过docker的http api查看存储的repository列表
curl http://localhost:5000/v2/_catalog
#输出结果为:{"repositories":["hello"]},hello的repository依然存在

查阅资料,发现官方指出:

  1. 目前的api只能删除repository中的镜像,而不能删除repository本身。
  2. 私有仓库提供的api只能删除manifests(清单)和layers(层)。所以repository一旦被创建,将无法通过api将其彻底删除。
  3. api中的删除操作会移除对目标的引用,使得其可以被垃圾回收。同时让它无法被api访问。
  4. 垃圾回收会清理不被任何manifests(清单)引用的数据块,数据块包含layers(层)或manifests(清单)。当manifests(清单)被删除时,它指向的layers(层)中没被其他清单引用的也会被删除。

也就是说之前通过api删除的,只是repository下的hello:latest镜像,而repository本身依然存在。
想要删除repository,需要通过一种stop-the-world(清理期间上传中的镜像可能会被误删)的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#查找私有仓库的容器ID
docker container ls | grep registry:2
#容器执行指令
# exec 让容器执行指令
# -it i为交互,t为分配一个冒充的tty
# 74252955fbd9 私有仓库的容器ID,替换为实际ID
# /bin/sh 让容器执行的指令
docker exec -it 74252955fbd9 /bin/sh #执行后可以直接通过shell操作容器了
#进行垃圾回收
# 垃圾回收会清理不被任何manifests(清单)引用的数据块,数据块包含layers(层)或manifests(清单)
# 当manifests(清单)被删除时,它指向的layers(层)中没被其他清单引用的也会被删除
/bin/registry garbage-collect /etc/docker/registry/config.yml
#删除hello的repository
rm -rf /var/lib/registry/docker/registry/v2/repositories/hello

#通过docker的http api查看存储的repository列表
curl http://localhost:5000/v2/_catalog
#输出结果为:{"repositories":[]},说明已经hello已经被彻底删除
通过Dockerfile生成镜像

Get Started, Part 2: Containers
Best practices for writing Dockerfiles

导言

这里将通过一个简单的场景来学习Dockerfile的使用:制作一个安装了openresty的centos的镜像(image)。

1
2
3
4
5
6
7
8
9
10
11
12
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
Kubernetes:
Version: v1.10.11
StackAPI: v1beta2

一. 创建一个工作目录

1
2
# 后续这个目录将存放Dockerfile和entrypoint.sh
mkdir hello

二. 在工作目录中创建一个Dockerfile文件

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
# 指定基础镜像,并且必须是第一条指令(如果不需要基础镜像,那么替换为 FROM scratch)
# 这里的centos是镜像名,7是标签。这里对应系统centos 7
FROM centos:7

# 容器的工作路径,对后续的RUN, CMD, ENTRYPOINT, COPY, ADD生效(如果对应的目录不存在则会创建,可以重复设置)
# 这里会在容器中创建一个/app文件夹,下面的对应操作会在/app中进行
WORKDIR /app

# 从本地拷贝文件到容器
# 这里会把本地的demo目录内的全部文件拷贝到容器的/app目录下(./指当前目录,而当前目录通过WORKDIR定义)
COPY * ./

# 容器在build时执行指令
RUN yum install -y lsof git
RUN yum install -y yum-utils
RUN yum-config-manager -y --add-repo https://openresty.org/package/centos/openresty.repo
RUN yum install -y openresty

# 容器启动时执行指令,CMD命令的功能类似,但主要区别在于CMD可被dock run的COMMAND参数覆盖,而ENTRYPOINT不会
# 如果CMD或ENTRYPOINT配置了多条,且CMD指令不是完整的可执行命令,那么CMD的内容将成为ENTRYPOINT的参数。反之仅最后一条生效。
# 当容器启动时会执行/app/entrypoint.sh文件(build的时候不会执行),该文件在上面的COPY操作中被docker从本地的工作目录拷贝到了容器的/app目录
ENTRYPOINT ./entrypoint.sh

# 对外开放端口
# 若果不将80端口对外暴露,容器外将无法访问容器的80端口
EXPOSE 80

三. 在工作目录中创建一个entrypoint.sh文件

1
2
3
4
5
6
7
#!/bin/sh

# 启动openresty服务
openresty

# docker会将没有前台守护进程的容器直接关闭,所以这里通过本指令阻塞脚本的执行
tail -f /dev/null

文件生成完成后,记得还要通过chmod +x entrypoint.sh给脚本加上执行权限,否则容器启动时将无法执行脚本。

四. 在工作目录下build镜像

Usage: docker build [OPTIONS] PATH | URL | -

1
2
3
# 待生成镜像的name为hello,tag为缺省值latest,路径为.(当前工作目录)
docker build -t hello .
# 指令执行完成后,控制台输出镜像完整的ID

执行docker image ls,docker会打印出本地存储的全部镜像,其中REPOSITORY为hello,TAG为latest的记录对应刚创建的镜像。

五. 创建容器

Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

1
2
3
4
5
# -d 该容器在后台执行
# -p 4000:80 将容器的80端口映射到本地的4000端口(通过localhost:4000访问容器的openresty的80端口)。
# hello 使用镜像hello
docker run -d -p 4000:80 hello
# 指令执行完成后,控制台输出容器完整的ID

执行docker container ls,docker会打印出本地执行中的全部容器,其中IMAGE为hello的记录对应刚启动的容器。
打开浏览器访问http://localhost:4000,可以看到openrestry的默认主页,至此镜像及其对应的容器已创建完成。

Raft算法分析

导言

Raft算法用于保证分布式系统的强一致性,被目前知名的etcd所采用。

一. 心跳

集群中每个节点都需要定期向其他节点发送心跳包,如果超过一半的节点超过一定时间(阈值)未收到该节点的心跳包,那么认定该节点已下线。

二. 选举

集群中节点角色可为Leader、Follower或Candidate其中一个。当Follower在一定时间内没有收到来自Leader的心跳,会将自己角色变更为Candidate,然后等待一段随机的时间后,发起选举。

三. 强一致

假设集群中最多n台机器发生故障,那么最少需要2n+1个节点。比起在拜占庭问题中最少需要3n+1个节点,raft协议的优势在于节点不存在欺骗问题。