consul 部署

部署Consul可能会有一些坑,每个坑都可能导致非常严重的事故。

配置

配置详情参考官方文档。在配置Consul集群时,重点在初始化集群。初始化一个Consul集群,首先需要部署主节点,并选举出Leader,然后部署代理节点,加入集群。

部署主节点初始化集群,有两种方式。一种是指定Leader,并首先部署(bootstrap设置为true),其他主节点配置加入指定的Leader;另一种是不指定Leader(bootstrap设置为false),在主节点达到期望的数量时(bootstrap_expect),自动选举。第二种相对一第一种,更简单,且不容易出错。

master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"client_addr": "0.0.0.0",
"datacenter": "consul-example",
"data_dir": "/srv/consul/consul.d",
"domain": "dev",
"dns_config": {
"enable_truncate": true,
"only_passing": true
},
"enable_syslog": true,
"encrypt": "MUEfHaiegJAg9oKogQYG9G5xDg3Bp1eBe/wd4OpoWwE=",
"leave_on_terminate": true,
"log_level": "INFO",
"rejoin_after_leave": true,
"server": true,
"bootstrap": false,
"bootstrap_expect": 3,
"retry_join": [
"172.17.0.2",
"172.17.0.3",
"172.17.0.4"
],
"ui": true
}

注意事项

  • 在docker中,enable_syslog 需要设置为false,否则可能会启动失败
  • encrypt 需要使用命令 consul keygen 重新生成并更新

agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"client_addr": "0.0.0.0",
"datacenter": "consul-example",
"data_dir": "/srv/consul/consul.d",
"domain": "dev",
"dns_config": {
"enable_truncate": true,
"only_passing": true
},
"enable_syslog": true,
"encrypt": "MUEfHaiegJAg9oKogQYG9G5xDg3Bp1eBe/wd4OpoWwE=",
"leave_on_terminate": true,
"log_level": "INFO",
"rejoin_after_leave": true,
"server": false,
"bootstrap": false,
"retry_join": [
"172.17.0.2",
"172.17.0.3",
"172.17.0.4"
],
"ui": false
}

部署

下载容器

1
$ docker pull ubuntu:18.04

主节点

创建容器

1
2
3
$ docker run -it -d --restart=always -p 8500:8500 --name=consul-srv1 ubuntu:18.04 # 绑定端口,用于访问界面
$ docker run -it -d --restart=always -p 8501:8501 --name=consul-srv2 ubuntu:18.04 # 可以不绑定端口
$ docker run -it -d --restart=always -p 8502:8502 --name=consul-srv3 ubuntu:18.04 # 可以不绑定端口

获取IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 登录容器
$ docker exec -it consul-srv1 /bin/bash

# 初始化容器
$ apt update && apt install -y iproute2 wget unzip vim

# 获取IP
## 1. 通过 hosts 文件获取
$ cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 e7cdefc0954c
## 2. 通过 ip 命令获取
$ apt update && apt install iproute2
$ ip address
...
inet 172.17.0.2/16
...
容器 ip
consul-srv1 172.17.0.2
consul-srv2 172.17.0.3
consul-srv3 172.17.0.4

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建服务目录
$ mkdir /srv/consul && cd /srv/consul
# 下载 consul
$ wget https://releases.hashicorp.com/consul/1.9.0/consul_1.9.0_linux_amd64.zip
# 解压
$ unzip consul_1.9.0_linux_amd64.zip
# 添加配置文件
$ vim config.json
# 写入配置文件,把 enable_syslog 设为 false

# 添加启动脚本
$ vim run-consul.sh # 输入如下内容
[ ! -d /srv/consul/consul.d ] && mkdir /srv/consul/consul.d
config_dir="/srv/consul/config.json"
host_name="`hostname --fqdn`"
host_ip=`hostname -I | awk '{print $1}'`
consul agent -config-dir="$config_dir" -bind=$host_ip

启动

1
$ export PATH=.:$PATH && bash run-consul.sh

访问UI界面

浏览器打开地址 localhost:8500

代理节点

创建容器

1
$ docker run -it -d --restart=always --name=consul-agt1 ubuntu:18.04

初始化

首先登陆容器。

1
2
3
4
5
# 登录容器
$ docker exec -it consul-agt1 /bin/bash

# 初始化容器
$ apt update && apt install -y iproute2 wget unzip vim

初始化和主节点一致。

启动

1
$ export PATH=.:$PATH && bash run-consul.sh

访问界面,可以看到Agent信息。

更换主节点

如果我们想更换掉所有的主节点,需要如下步骤。

  • 部署新的主节点服务,并加入到集群,同步集群数据
  • 更新所有代理节点的主节点配置文件,并重启
  • 下线旧主节点

说明

  • bootstrap_expect 主要作用于集群初始化阶段,只有发现足够的主节点,才会启动选主程序,并初始化。集群初始化后,停止所有主节点,并启动其中一个, 会根据历史数据,启动集群,此时 bootstrap_expect 配置的值没有意义。

最佳实践

  • 设置 encrypt,不同机房,使用不同 datacenter 名称,避免跨环境注册,服务混合
  • 设置 limits.http_max_conns_per_client ,默认 200,极有可能不够用,出现各种问题

配置

Standalone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"retry_join" : ["127.0.0.1"],
"data_dir": "/var/lib/consul",
"log_level": "INFO",
"server": true,
"node_name": "master",
"addresses": {
"http": "0.0.0.0"
},
"bind_addr": "0.0.0.0",
"advertise_addr": "{ip}",
"ui": true,
"bootstrap_expect": 1
}

django viewset

自定义 Action

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework.decorators import action


class SnippetViewSet(viewsets.ModelViewSet):
...

@action(detail=False, methods=['GET'], name='Get Highlight')
def highlight(self, request, *args, **kwargs):
queryset = models.Highlight.objects.all()

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

跨域

webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config/index.js
module.exports = {
dev: {
...
proxyTable: {
'/api': {
target: "http://localhost:8000",
changeOrigin: true,
pathRewrite: {
'^/api': 'api'
}
},
},
...
}
}

django 常见问题

启动时执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 在 app 目录下的 apps.py 中定义 Config

from django.apps import AppConfig


class JobConfig(AppConfig):
name = 'job'

def ready(self):
from jobs import scheduler # jobs 为 app 名称
scheduler.start() # scheduler.start() 是要做的事情; 这里是启动一个定时器

# 在 app 目录下的 __init__.py 中添加
import os

if os.environ.get('RUN_MAIN', None) != 'true': # 加这个判断,可以防止执行多次
default_app_config = 'jobs.apps.JobConfig'

# 注: 使用manage.py时,也有可能会运行

CORS

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
# install django-cors-headers
$ pip install django-cors-headers

# add apps in settings.py
INSTALLED_APPS = [
...
'corsheaders',
...
]

# add middleware in settings.py
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]

# add config in settings.py
CORS_ORIGIN_ALLOW_ALL = True # If this is used then `CORS_ORIGIN_WHITELIST` will not have any effect
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
'http://localhost:3030',
] # If this is used, then not need to use `CORS_ORIGIN_ALLOW_ALL = True`
CORS_ORIGIN_REGEX_WHITELIST = [
'http://*.example.com:3030',
]

Docker 容器

MySQL & mariadb

MySQL

1
2
$ docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest --default-authentication-plugin=mysql_native_password
# 认证插件 mysql_native_password, passwd, sha256_password; 如果工具连接出错,可尝试添加

mariadb

下载镜像

1
$ docker pull mariadb:latest

运行镜像

1
$ docker run -p 3306:3306 --name mariadb -e MYSQL_ROOT_PASSWORD=ipwd -d mariadb:latest

参考

Redis

下载镜像

1
$ docker pull redis:latest

运行容器

1
$ docker run -itd --name redis -p 6379:6379 redis:latest

集群模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ docker pull redis

docker create --name redis-node1 -p 6379:6379 redis --cluster-enabled yes
docker create --name redis-node2 -p 6378:6378 redis --cluster-enabled yes --port 6378
docker create --name redis-node3 -p 6377:6377 redis --cluster-enabled yes --port 6377
docker start redis-node1 redis-node2 redis-node3

docker exec -it redis-node1 /bin/sh

> redis-cli --cluster create 172.17.0.2:6379 172.17.0.3:6379 172.17.0.4:6379

> redis-cli --cluster create 192.168.4.13:6379 192.168.4.13:6378 192.168.4.13:6377

# 查看容器 ip
$ docker inspect <container-name>

# stop
docker stop redis-node1 redis-node2 redis-node3

# rm
docker rm redis-node1 redis-node2 redis-node3

alpine

1
2
$ docker pull alpine:latest
$ docker run -itd --name alpine-ins alpine:latest

docker truble shooting

3128 proxy

macos desktop pull 镜像时遇到 proxyconnect 错误。

1
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": proxyconnect tcp: dial tcp 172.17.0.1:3128: connect: connection refused.

搞不定了,把容器全删了之后好了。

1
2
# 删除所有容器数据
rm -rf ~/Library/Containers/com.docker.docker

docker usage

Docker

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

安装

ubuntu

参考官网文档

amazon linux 2

1
2
3
sudo amazon-linux-extras install epel -y
sudo amazon-linux-extras enable docker
sudo yum install -y docker

配置

修改 data 目录

1
2
3
4
# vim /etc/docker/daemon.json
{
"data-root": "/data/docker"
}

容器

从镜像创建新容器

1
2
3
4
$ docker run -it -d --restart=always --name=ubuntu-18.04 ubuntu:18.04

# 指定端口
$ docker run --restart=always -p 8080:8080 <image-name>

启动已创建容器

1
docker start container_id/container_name

查看运行的容器

使用docker ps命令可以查看所有正在运行中的容器列表,使用docker inspect命令我们可以查看更详细的关于某一个容器的信息。

1
docker ps

停止运行容器

1
docker stop container-name

示例

为了显示更直观, 删除部分内容并使用省略号代替.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@VirtualBox:/home/conpot# docker ps
CONTAINER ID IMAGE ...
5a794455532d nginx:alpine ...
8518250908b5 voxxit/rsyslog ...
77613e26eb6f elk_logstash ...
7effd23c7005 elk_kibana ...
root@VirtualBox:/home/conpot# docker stop 5a794455532d
5a794455532d
root@VirtualBox:/home/conpot# docker ps
CONTAINER ID IMAGE ...
8518250908b5 voxxit/rsyslog ...
77613e26eb6f elk_logstash ...
7effd23c7005 elk_kibana ...
root@VirtualBox:/home/conpot# docker stop 8518
8518
root@VirtualBox:/home/conpot# docker ps
CONTAINER ID IMAGE ...
77613e26eb6f elk_logstash ...
7effd23c7005 elk_kibana ...

停止所有运行的容器

1
2
3
4
5
6
7
root@VirtualBox:/home/conpot# docker ps -q
77613e26eb6f
7effd23c7005
root@VirtualBox:/home/conpot# docker stop $(docker ps -q)
77613e26eb6f
7effd23c7005
root@VirtualBox:/home/conpot# docker ps -q

重命名容器

1
docker rename CONTAINER NEW_NAME

更新容器设置

1
docker update --restart=always container_name/container_id # --restart=no

执行命令

1
docker exec -it [container] /bin/bash

列出所有容器

1
docker ps -a

删除容器

1
docker docker rm container-name

迁移容器

1
2
3
4
5
6
7
8
9
10
11
12
# 为 container 创建镜像
$ sudo docker commit <container-name> <image-name>
# 导出镜像
$ sudo docker save <image-name> > image-name.tar
# sudo docker save <image-name> -o image-name.tar

#####

# 导入镜像
$ sudo docker load < image-name.tar
# 创建 container
$ sudo docker run -d --name <container-name> ... <image-name>

为容器添加持久化存储

使用本地存储

1
2
3
$ docker run -d --name {name} --restart always \
-v /path/to/local/system:/container/path \
{image}

使用 Docker Volumn

1
2
3
4
$ docker volume create {volumn-name}
$ docker run -d --name {name} --restart always \
-v {volumn-name}:/container/path \
{image}

镜像

导出镜像

1
2
3
4
5
$ docker save busybox > busybox.tar
$ docker save -o fedora-all.tar fedora
$ docker save -o fedora-latest.tar fedora:latest
# 压缩
$ docker save myimage:latest | gzip > myimage_latest.tar.gz

加载镜像

1
2
docker load -i docker-output.tar
docker load < docker-output.tar
1
docker search ubuntu

下载

1
docker pull

查看下载的容器

1
2
docker images
docker images ubuntu # 查看单个镜像

分析镜像大小

参考工具 dive

1
dive hub.docker.com/<user>/<image>:<tag>

删除镜像 / 清理

1
2
3
docker image remove <id>
docker image prune # 删除无用镜像
docker image prune -a # 删除所有镜像

导出/加载容器

1
2
docker export container-name > latest.tar
docker export --output="latest.tar" container-name

非 root 用户使用 docker

docker root

ubuntu

https://www.jianshu.com/p/35cdb71a32d3

1
2
3
4
5
6
$ sudo groupadd docker
$ sudo usermod -aG docker $(whoami)

# 生效
$ sudo service docker restart
$ newgrp - docker # 切换到docker用户组

通过 snap 安装的 docker

1
2
3
4
5
sudo addgroup --system docker
sudo adduser $USER docker
newgrp docker
sudo snap disable docker
sudo snap enable docker

macos

bash rc 添加如下内容。

1
2
3
4
unset DOCKER_TLS_VERIFY
unset DOCKER_CERT_PATH
unset DOCKER_MACHINE_NAME
unset DOCKER_HOST

拷贝文件

从容器拷贝至宿主机

1
docker cp <container-name>:/path/to/file /path/to/dest

从宿主机拷贝至容器

1
docker cp /path/to/file <container-name>:/path/to/dest

docker hub

1
2
3
4
5
# 登录 docker hub
docker login

# 登录私有仓库
docker login hub.private.com

配置代理

~/.docker/config.json

注意,这是 docker cli 的配置,不是 daemon 的配置。

1
2
3
4
5
6
7
8
9
{
"proxies": {
"default": {
"httpProxy": "http://proxy.example.com:3128",
"httpsProxy": "https://proxy.example.com:3129",
"noProxy": "*.test.example.com,.example.org,127.0.0.0/8"
}
}
}

/etc/docker/daemon.json

注意:

  • 这是 daemon 的配置,不是 docker cli 的配置
  • httpProxy (docker cli)和 http-proxy(docker daemon)的差别
  • https-proxy 配置成 http 也是可以的,比如 "https-proxy": "http://proxy.example.com:3128"
1
2
3
4
5
6
7
{
"proxies": {
"http-proxy": "http://proxy.example.com:3128",
"https-proxy": "https://proxy.example.com:3129",
"no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
}
}

重启生效。

1
2
3
sudo systemctl restart docker
#
sudo service docker restart

其他

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo touch /etc/systemd/system/docker.service.d/proxy.conf
sudo chmod 777 /etc/systemd/system/docker.service.d/proxy.conf
sudo echo '
[Service]
Environment="HTTP_PROXY=socks5://192.168.6.19:3213"
Environment="HTTPS_PROXY=socks5://192.168.6.19:3213"
' >> /etc/systemd/system/docker.service.d/proxy.conf
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart kubelet

参考

配置网络

centos

1

修改 /var/lib/docker 路径

1
sudo vim /lib/systemd/system/docker.service

参考

异常

failed to start daemon: Devices cgroup isn't mounted

1
2
3
4
5
6
yum install libcgroup libcgroup-tools libcgroup-pam
systemctl enable cgconfig
systemctl start cgconfig

# 重启
reboot

常用操作

查看磁盘占用

1
2
3
4
5
docker system df -v
# 会打印 image、container 的磁盘占用

# 查看容器磁盘占用
docker ps --size

清理无用镜像

1
2
3
4
5
6
7
8
docker image prune # 仅清理镜像

# !危险操作
docker system prune # 要同时清理未使用的镜像、停止的容器、未使用的网络和未挂载的卷

# !危险操作,可能误删
# 尝试删除所有镜像
docker image ls -a | awk '{print $3}' | xargs docker image rm -

镜像推荐

TroubleShooting

dockerd

有时候,dockerd 会给出一些错误信息。

1
2
~/code/private-config/nodes/home$ dockerd
unable to configure the Docker daemon with file /etc/docker/daemon.json: the following directives don't match any configuration option: noProxy