Docker Register 安装

  • docker pull registry

  • docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 –restart=always –name registry registry:2

  • docker images 查看 register 的id

docker inspect id,发现/var/lib/registry 是放镜像的地方,用-v导出来。

  • 访问ip:5000/v2/xxx/tags/list,其中xx是镜像属性

  • push 到私有Register

step1——找到本地镜像的ID:docker images

step2——登陆Hub:docker login –username=username –password=password –email=email

step3——tag:docker tag /:

​ docker tag hello-world:latest localhost:5000/hello-mine:latest

step4——push镜像:docker push /

  • https修改

修改 /etc/sysconfig/docker

INSECURE_REGISTRY=’–insecure-registry 10.0.13.64:5000’

重启docker服务,服务连接正常。

systemctl restart docker.service

再谈Docker容器单机网络:利用iptables trace和ebtables log

这大半年一直在搞Kubernetes。每次搭建Kubernetes集群,或多或少都会被Kubernetes的“网络插件们”折腾折腾。因此,要说目前Kubernetes中最难搞的是什么?个人觉得莫过于其Pod网络了,至少也是最难搞的之一。除此之外,以Service和Pod为中心的Kubernetes架构还大量利用iptables规则来实现Service的反向代理和负载均衡,这又与Docker原生容器单机网络实现所基于的linux bridge和iptables规则糅合在一起,让troubleshooting时的难度又增加了一些。

去年曾经花过一段研究Docker网络,但现在看来当时在某些关键环节的理解上还有些模糊,于是花了周末的闲暇时间对Docker容器单机网络做了一次再理解。这次重新认识利用上了iptables的Trace功能以及数据链路层的ebtables,让我可以更清晰地看到单机容器网络的网络数据流流向。同时,有了容器网络理解这个基础,对后续解决K8s Pod网络问题也是大有裨益的。

本文从某个角度来说也可以理解为自我答疑,我不会从最最基础的Docker网络结构说起,对Docker容器单机网络结构不了解的童鞋,可以先看看我之前写的《理解Docker单机容器网络》和《理解Docker容器网络之Linux Network Namespace》两篇文章。

一、实验环境

1、主机环境和工具版本

Docker的默认单机容器网络从最初的版本开始就几乎没有变过,因此理论上下面的分析适用于Docker的大部分版本。我的实验环境如下:

Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-63-generic x86_64)

# docker version
Client:
 Version:      17.09.0-ce
 API version:  1.32
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:42:18 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.09.0-ce
 API version:  1.32 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   afdb6d4
 Built:        Tue Sep 26 22:40:56 2017
 OS/Arch:      linux/amd64
 Experimental: false

# iptables --version
iptables v1.6.0
# ebtables --version
ebtables v2.0.10-4 (December 2011)

2、容器网络及拓扑

我们需要制作一个用于实验的容器镜像。因为这里仅用ping包进行测试,这里我们仅基于ubuntu:14.04 base image制作一个简单的安装有必要网络工具的image:

//Dockerfile

From ubuntu:14.04
RUN apt-get update && apt-get install -y curl iptables
ENTRYPOINT ["tail", "-f", "/var/log/bootstrap.log"]

// 制作镜像:

# docker build -t foo:latest ./

启动两个容器:

# docker run --name c1 -d --cap-add=NET_ADMIN foo:latest
7a01a19d9328b39f094c9a9c76340d179baaf93afb52189816bcc79f8319cb64
# docker run --name c2 -d --cap-add=NET_ADMIN foo:latest
94a2f1841f6d95fd0682299b17c0aedb60c1047786c8e75b0f1ab7316a995409

容器启动后的网络信息汇总如下:

# ifconfig -a
docker0   Link encap:Ethernet  HWaddr 02:42:ff:27:17:4d
          inet addr:192.168.0.1  Bcast:0.0.0.0  Mask:255.255.240.0
          ... ...

eth0      Link encap:Ethernet  HWaddr 00:16:3e:06:3a:3a
          inet addr:10.171.77.0  Bcast:10.171.79.255  Mask:255.255.248.0
          ... ...

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          ... ...

veth0594f4b Link encap:Ethernet  HWaddr 96:5b:d4:80:73:5f
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          ... ...

veth57a3dec Link encap:Ethernet  HWaddr 02:52:e9:60:ea:b1
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          ... ...

为了方便大家理解,这里附上一幅简易的容器网络拓扑:

未分类

二、调试工具配置

Docker单机容器网络默认使用的是桥接网络,所有启动的容器均桥接在Docker引擎创建的docker0 linux bridge上,因此内核对Linux bridge的处理逻辑是理解Docker容器网络的关键。

与硬件网桥/交换机不同的是,Linux Bridge还具备三层网络,即IP层的功能,也就是docker0既是一个网桥也是一个具备三层转发功能的网卡设备。传统意义上,按照iso网络七层规范,iptables工作在三层,而网桥是一个二层(数据链路层)设备,但Linux协议栈针对网桥设备的实现却在网络层的规则链(ebtables)中串接了iptables的规则链处理,即在二层也可以处理ip包,这是为了实现桥接透明防火墙的需要。但实现也会保证每个packet数据包仅会走一次iptable的某个chain,要么在linker layer走,要么在network layer走,不会出现在linker layer走一次,又在network layer重复走一次的情况。关于这种基于linux bridge的ebtables和iptables的交互规则,在netfilter官网的一篇名为《ebtables/iptables interaction on a Linux-based bridge》文档中有详细说明,这篇文章也是后续分析的一个重要参考。下面这幅图也是文章中提到的那幅netfilter数据流全图,后续在分析时会反复回到这幅图(后续简称为:全图):

未分类

建议:右键在新标签中打开图片看大图

关于数据包在iptables的各条chain的流经图可以参见下面:

未分类

1、iptables TRACE target的设置

在本次实验中,我们主要需要查看数据包的流转路径,因此我们需要针对iptables的data flow进行跟踪。之前,我曾使用过iptables提供的LOG target或mark set&match方式来跟踪iptables中的数据流,但这两种方式都不理想,需要针对特定流程插入LOG target或match在入口包设定好的mark,对iptables规则的侵入较大,调试和观察也较为复杂;iptables自身提供了TRACE功能,一旦设定,当数据包匹配到任意chain上任意table的处理规则时,iptables会在系统日志(/var/log/syslog)中自动输出此时的数据包状态日志。

我们来为iptables规则添加TRACE,TRACE target只能在iptables的raw表中添加,raw表中有两条iptables built-in chain: PREROUTING和OUTPUT,分别代表网卡数据入口和本地进程下推数据的出口。TRACE target就添加在这两条chain上,步骤如下:

# iptables -t raw -A OUTPUT -p icmp -j TRACE
# iptables -t raw -A PREROUTING -p icmp -j TRACE

注意:我们采用icmp协议(ping协议)进行测试,因此我们只TRACE icmp协议的请求和应答包。

2、ebtables的调试设置

我们的重点在iptables,为ebtables只是辅助,帮助我们看清数据包到底是在哪一层被hook进iptables的规则链中进行处理的。因此我们在全图中的每个ebtables的built-in chain上都加上LOG(ebtables目前还不支持TRACE):

# ebtables -t broute -A BROUTING -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:broute:BROUTING" -j ACCEPT
# ebtables -t nat -A OUTPUT -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:OUTPUT"  -j ACCEPT
# ebtables -t nat -A PREROUTING -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:PREROUTING" -j ACCEPT
# ebtables -t filter -A INPUT -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:INPUT" -j ACCEPT
# ebtables -t filter -A FORWARD -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:FORWARD" -j ACCEPT
# ebtables -t filter -A OUTPUT -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:OUTPUT" -j ACCEPT
# ebtables -t nat -A POSTROUTING -p ipv4 --ip-proto 1 --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:POSTROUTING" -j ACCEPT

注意:这里–ip-proto 1 表示仅match icmp packet。

3、iptables和ebtables规则全文

启动两个容器并添加上述规则后,当前的的iptables规则如下:(通过iptables-save输出的按table组织的rules)

# iptables-save
# Generated by iptables-save v1.6.0 on Sun Nov  5 14:50:46 2017
*raw

: PREROUTING ACCEPT [1564539:108837380]
:OUTPUT ACCEPT [1504962:130805835]
-A PREROUTING -p icmp -j TRACE
-A OUTPUT -p icmp -j TRACE
COMMIT
# Completed on Sun Nov  5 14:50:46 2017
# Generated by iptables-save v1.6.0 on Sun Nov  5 14:50:46 2017
*filter
:INPUT ACCEPT [1564535:108837044]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [1504968:130806627]

: DOCKER - [0:0]

: DOCKER-ISOLATION - [0:0]

: DOCKER-USER - [0:0]

-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Sun Nov  5 14:50:46 2017
# Generated by iptables-save v1.6.0 on Sun Nov  5 14:50:46 2017
*nat

: PREROUTING ACCEPT [280:14819]
:INPUT ACCEPT [278:14651]
:OUTPUT ACCEPT [639340:38370263]

: POSTROUTING ACCEPT [639342:38370431]

: DOCKER - [0:0]

-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 192.168.0.0/20 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Sun Nov  5 14:50:46 2017

而ebtables的规则如下:

# ebtables-save
# Generated by ebtables-save v1.0 on Sun Nov  5 16:51:50 CST 2017
*nat
: PREROUTING ACCEPT
:OUTPUT ACCEPT
: POSTROUTING ACCEPT
-A PREROUTING -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:nat:PREROUTING" --log-ip -j ACCEPT
-A OUTPUT -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:nat:OUTPUT" --log-ip -j ACCEPT
-A POSTROUTING -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:nat:POSTROUTING" --log-ip -j ACCEPT

*broute
:BROUTING ACCEPT
-A BROUTING -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:broute:BROUTING" --log-ip -j ACCEPT

*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
-A INPUT -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:filter:INPUT" --log-ip -j ACCEPT
-A FORWARD -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:filter:FORWARD" --log-ip -j ACCEPT
-A OUTPUT -p IPv4 --ip-proto icmp --log-level info --log-prefix "TRACE: eb:filter:OUTPUT" --log-ip -j ACCEPT

对于iptables,我们还可以通过iptables命令输出另外一种组织形式的规则列表,我们这里列出filter和nat这两个重要的table的规则(输出规则number,便于后续match分析时查看):

# iptables -nL --line-numbers -v -t filter
Chain INPUT (policy ACCEPT 2558K packets, 178M bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       10   840 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
2       10   840 DOCKER-ISOLATION  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3        7   588 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4        3   252 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
5        0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
6        3   252 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 2460K packets, 214M bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
num   pkts bytes target     prot opt in     out     source               destination

Chain DOCKER-ISOLATION (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1       10   840 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1       10   840 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

# iptables -nL --line-numbers -v -t nat
Chain PREROUTING (policy ACCEPT 884 packets, 46522 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1      881 46270 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 881 packets, 46270 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 1048K packets, 63M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 1048K packets, 63M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 MASQUERADE  all  --  *      !docker0  192.168.0.0/20       0.0.0.0/0

Chain DOCKER (2 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0

三、Container to Container

下面,我们分三种情况来看看容器网络的数据包是如何流动的,首先是Container to Container。

未分类

我们在容器C1中执行ping 3次 C2的命令:

# docker exec c1 ping -c 3 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.226 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.159 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.185 ms

--- 192.168.0.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.159/0.190/0.226/0.027 ms

在容器c1(192.168.0.2)中,icmp request由ping程序(c1 namespace中的local process)发出。c1 network namespace中的路由表如下:

# docker exec c1 netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         192.168.0.1     0.0.0.0         UG        0 0          0 eth0
192.168.0.0     0.0.0.0         255.255.240.0   U         0 0          0 eth0

由于目标容器地址为192.168.0.3,在容器c1的直连网络上,走第二条直连路由(非默认路由),数据包通过eth0发出。

由于c1 namespace中的eth0通过veth机制连接在host namespace的docker0 bridge的一个Slave port上,因此上述数据包通过docker0 bridge的slave port: veth0594f4b流入docker0 bridge。

这里再强调一下linux bridge设备。Linux下的Bridge是一种虚拟设备,它依赖于一个或多个从设备。它不是内核虚拟出的和从设备同一层次的镜像设备,而是内核虚拟出的一个高一层次的设备,并把从设备虚拟化为端口port,同时处理各个从设备的数据收发及转发。bridge设备是建立在从设备之上的(这些从设备可以是实际设备,也可以是vlan设备等),并且我们可以为bridge准备一个IP(bridge设备的MAC地址是它所有从设备中最小的MAC地址),这样该主机就可以通过这个bridge设备与网络中的其它主机通信了。另外一旦某个网络设备被“插到”linux bridge上,这个网络设备将会变为bridge的从设备,被虚拟化为端口port,从设备的IP及MAC都不再可用,好似被bridge剥夺了被内核网络栈处理的资格;它们被设置为接收任何包,对其流入的数据包的处理交由bridge完成,并最终由bridge设备来决定数据包的去向:接收到本机、转发或丢弃。

因此,位于host namespace的docker0 bridge从slave port: veth0594f4b收到icmp request后,我们不会看到veth0594f4b这一netdev被内核网络栈程序单独处理(比如:单独走一遍ebtables和iptables chains),而是进入bridge处理逻辑(此时可以回顾一下上面的全图)。由于数据包已经进入到了host namespace,因此我们可以通过ebtables和iptables输出的Trace和log来跟踪数据包流转的路径了:

1、start -> bridgecheck -> linker layer

TRACE: eb:broute:BROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: eb:nat:PREROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1

从最初的trace log来看,在bridge check之后(发现it is a linux bridge),数据包进入到linker layer中;并且在linker layer的BROUTING built-in chain之后,数据包没有被转移到上面的network layer,而是继续linker layer的行程:进入linker layer的nat:PREROUTING中。

2、call iptables chain rules in linker layer

结合全图中的图示和日志输出,在linker layer的nat:PREROUTING之后,linker layer调用了上层iptables的处理规则:raw:PREROUTING和nat:PREROUTING:

TRACE: raw:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: nat:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1

Trace target在数据包match table、chains的policy或rules时会输出日志,日志格式:”TRACE:tablename:chainname:type:rulenum”。当匹配到的是普通rules时,type=”rule”;当碰到一个user-defined chain的return target时,type=”return”;当匹配到built-in chain(比如:PREROUTING、INPUT、OUTPUT、FORWARD和POSTROUTING)的default policy时,type=”policy”。

从上面的日志输出来看,似乎PREROUTING chain的raw table中的Trace target不能被trace自身match,因此trace log输出的是匹配raw table built-in chain: PREROUTING的default policy: ACCEPT,num=2(policy和rules整体排序后的序号);在PREROUTING chain的nat表中匹配时,Trace也仅匹配到了default policy,rule 1(target: Docker)没有匹配上;

这里有一点奇怪的是mangle table没有任何输出,即便是default policy的也没有,原因暂不明。

3、bridge decision

根据全图和后续的日志,我们得到了bridge decision的结果:继续在linker layer上处理数据包,一路向右。不过在处理的路径上依旧调用了iptables的rules:

TRACE: eb:filter:FORWARD IN=veth0594f4b OUT=veth57a3dec MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: filter:FORWARD:rule:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:DOCKER-USER:return:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:FORWARD:rule:2 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:DOCKER-ISOLATION:return:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:FORWARD:rule:4 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:DOCKER:return:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1
TRACE: filter:FORWARD:rule:6 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1

bridge decision决定的依据或则规则是什么呢?《ebtables/iptables interaction on a Linux-based bridge》一文给了我们一些答案:

The bridge's decision for a frame can be one of these:

* bridge it, if the destination MAC address is on another side of the bridge;
* flood it over all the forwarding bridge ports, if the position of the box with the destination MAC is unknown to the bridge;
* pass it to the higher protocol code (the IP code), if the destination MAC address is that of the bridge or of one of its ports;
* ignore it, if the destination MAC address is located on the same side of the bridge.

不过即便按照这几条规则,我依然有一定困惑,那就是真实的处理是:依旧在linker layer,但掺杂了上层网络层的处理规则。

另外,你可能会发现iptables log里MAC值的格式很怪异(比如:MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00),非常long。其实这个MAC值是一个组合:Souce MAC, Destination MAC和 frame type的组合。

02:42:c0:a8:00:03: Destination MAC=00:60:dd:45:67:ea
02:42:c0:a8:00:02: Source MAC=00:60:dd:45:4c:92
08:00 : Type=08:00 (ethernet frame carried an IPv4 datagram)

4、eb:nat:POSTROUTING -> nat:POSTROUTING -> egress(qdisc)

最后packet进入linker layer的POSTROUTING built-in chain:

TRACE: eb:nat:POSTROUTING IN= OUT=veth57a3dec MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: nat:POSTROUTING:policy:2 IN= OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47066 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=1

iptables nat:POSTROUTING没有匹配上docker引擎增加的那条target为DOCKER的rule,于是输出了default policy的日志。

进入到egress(qdisc)后,相当于数据包到了bridge上的另一个slave port(veth57a3dec)上,此时数据包必须被送回网络上,于是进入到容器C2的eth0中。离开了host namespace,我们的日志便追踪不到了。

容器c2因为所在的network namespace是独立于host namespace的,因此有自己的iptables规则(如果未设置,均为默认accept),不受host namespace中的iptables的影响。

5、”消失”的iptable的nat:PREROUTING和nat:POSTROUTING

C2容器回复ping response的路径与request甚为相似,这里一次性将全部日志列出:

TRACE: eb:broute:BROUTING IN=veth57a3dec OUT= MAC source = 02:42:c0:a8:00:03 MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.3 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:nat:PREROUTING IN=veth57a3dec OUT= MAC source = 02:42:c0:a8:00:03 MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.3 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: raw:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth57a3dec MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1

TRACE: eb:filter:FORWARD IN=veth57a3dec OUT=veth0594f4b MAC source = 02:42:c0:a8:00:03 MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.3 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: filter:FORWARD:rule:1 IN=docker0 OUT=docker0 PHYSIN=veth57a3dec PHYSOUT=veth0594f4b MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1
TRACE: filter:DOCKER-USER:return:1 IN=docker0 OUT=docker0 PHYSIN=veth57a3dec PHYSOUT=veth0594f4b MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1
TRACE: filter:FORWARD:rule:2 IN=docker0 OUT=docker0 PHYSIN=veth57a3dec PHYSOUT=veth0594f4b MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1
TRACE: filter:DOCKER-ISOLATION:return:1 IN=docker0 OUT=docker0 PHYSIN=veth57a3dec PHYSOUT=veth0594f4b MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1
TRACE: filter:FORWARD:rule:3 IN=docker0 OUT=docker0 PHYSIN=veth57a3dec PHYSOUT=veth0594f4b MAC=02:42:c0:a8:00:02:02:42:c0:a8:00:03:08:00 SRC=192.168.0.3 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=5962 PROTO=ICMP TYPE=0 CODE=0 ID=90 SEQ=1

TRACE: eb:nat:POSTROUTING IN= OUT=veth0594f4b MAC source = 02:42:c0:a8:00:03 MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.3 IP DST=192.168.0.2, IP tos=0x00, IP proto=1

仔细观察,我们发现虽然与request的路径类似,但依旧有不同:iptable的nat:PREROUTING和nat:POSTROUTING消失了。Why?iptables就是这么设计的。iptables会跟踪connection的state,当一个connection的首个包经过一次后,connection的state由NEW变成了ESTABLISHED;对于ESTABLISHED的connection的后续packets,内核会自动按照该connection的首个包在nat:PREROUTING和nat:POSTROUTING环节的处理方式进行处理,而不再流经这两个链中的nat表逻辑。而ebtables中似乎没有这个逻辑。

后续的ping的第二个、第三个流程也印证了上述设计,这里仅列出ping request packet 2:

TRACE: eb:broute:BROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: eb:nat:PREROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: raw:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: eb:filter:FORWARD IN=veth0594f4b OUT=veth57a3dec MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1
TRACE: filter:FORWARD:rule:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: filter:DOCKER-USER:return:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: filter:FORWARD:rule:2 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: filter:DOCKER-ISOLATION:return:1 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: filter:FORWARD:rule:3 IN=docker0 OUT=docker0 PHYSIN=veth0594f4b PHYSOUT=veth57a3dec MAC=02:42:c0:a8:00:03:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.3 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=47310 DF PROTO=ICMP TYPE=8 CODE=0 ID=90 SEQ=2
TRACE: eb:nat:POSTROUTING IN= OUT=veth57a3dec MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:c0:a8:00:03 proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.3, IP tos=0x00, IP proto=1

全部日志内容请参见:docker-bridge-network-demo-iptables-trace-log.txt文件,这里不赘述。

四、Local Process to Container

未分类

很多”疑难”环节在上面的container to container数据流分析时已经做了解惑,因此后续local process to container和container to external流程将不会再细致描述,说明会略微泛泛一些,不那么细致。

我们在host上执行ping C1三次:

# ping -c 3 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.160 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.105 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=64 time=0.131 ms

--- 192.168.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.105/0.132/0.160/0.022 ms

1、local process -> routing decision -> iptables OUTPUT chain

ping request数据包从本地的ping process发出,根据目的地址路由后,选择docker0作为OUT设备:

TRACE: raw:OUTPUT:policy:2 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0
TRACE: mangle:OUTPUT:policy:1 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0
TRACE: nat:OUTPUT:policy:2 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0
TRACE: filter:OUTPUT:policy:1 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0

奇怪的是这次mangle chain居然有trace log输出:(。

2、进入linker layer:iptables POSTROUTING -> ebtables OUTPUT -> ebtables POSTROUTING

由于是OUT是bridge设备,因此要进入到ebtable中走一遭:

TRACE: mangle:POSTROUTING:policy:1 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0
TRACE: nat:POSTROUTING:policy:2 IN= OUT=docker0 SRC=192.168.0.1 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18692 DF PROTO=ICMP TYPE=8 CODE=0 ID=30245 SEQ=1 UID=0 GID=0
TRACE: eb:nat:OUTPUT IN= OUT=veth57a3dec MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:filter:OUTPUT IN= OUT=veth57a3dec MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:nat:POSTROUTING IN= OUT=veth57a3dec MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:nat:OUTPUT IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:filter:OUTPUT IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:nat:POSTROUTING IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=192.168.0.1 IP DST=192.168.0.2, IP tos=0x00, IP proto=1

icmp的response和container to container类似,入口走的是linker layer(由于是桥设备),在bridge decision后,走到INPUT chain:

TRACE: eb:broute:BROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:ff:27:17:4d proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.1, IP tos=0x00, IP proto=1
TRACE: eb:nat:PREROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:ff:27:17:4d proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.1, IP tos=0x00, IP proto=1
TRACE: raw:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=56535 PROTO=ICMP TYPE=0 CODE=0 ID=30245 SEQ=1
TRACE: mangle:PREROUTING:policy:1 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=56535 PROTO=ICMP TYPE=0 CODE=0 ID=30245 SEQ=1
TRACE: eb:filter:INPUT IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:ff:27:17:4d proto = 0x0800 IP SRC=192.168.0.2 IP DST=192.168.0.1, IP tos=0x00, IP proto=1
TRACE: mangle:INPUT:policy:1 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=56535 PROTO=ICMP TYPE=0 CODE=0 ID=30245 SEQ=1
TRACE: filter:INPUT:policy:1 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=192.168.0.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=56535 PROTO=ICMP TYPE=0 CODE=0 ID=30245 SEQ=1

以上我们可以与到非桥设备的ping做比对,我们在host上ping 另外一个LAN中的host:

# ping -c 1 10.28.61.30
PING 10.28.61.30 (10.28.61.30) 56(84) bytes of data.
64 bytes from 10.28.61.30: icmp_seq=1 ttl=57 time=1.09 ms

--- 10.28.61.30 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.093/1.093/1.093/0.000 ms

得到的trace log如下:

icmp request:

TRACE: raw:OUTPUT:policy:2 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0
TRACE: mangle:OUTPUT:policy:1 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0
TRACE: nat:OUTPUT:policy:2 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0
TRACE: filter:OUTPUT:policy:1 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0
TRACE: mangle:POSTROUTING:policy:1 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0
TRACE: nat:POSTROUTING:policy:2 IN= OUT=eth0 SRC=10.171.77.0 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4494 DF PROTO=ICMP TYPE=8 CODE=0 ID=30426 SEQ=1 UID=0 GID=0

icmp response:

TRACE: raw:PREROUTING:policy:2 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=61118 PROTO=ICMP TYPE=0 CODE=0 ID=30426 SEQ=1
TRACE: mangle:PREROUTING:policy:1 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=61118 PROTO=ICMP TYPE=0 CODE=0 ID=30426 SEQ=1
TRACE: mangle:INPUT:policy:1 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=61118 PROTO=ICMP TYPE=0 CODE=0 ID=30426 SEQ=1
TRACE: filter:INPUT:policy:1 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=61118 PROTO=ICMP TYPE=0 CODE=0 ID=30426 SEQ=1

可以对照着全图看出在request出去时,发现OUT设备不是bridge,直接走network layer的iptables rules,并从xfrm lookup出去,走到egress(qdisc); response回来时,进行bridge check后,发现IN设备eth0不是bridge,因此直接上到network layer,走iptable chain rules到local process。ebtable的log一行也没有输出。

后续的两个icmp request&response大致相同,并且依旧不走nat PREROUTING和nat POSTROUTING,因为不再是NEW connection。

五、Container to External

未分类

我们在c1 容器中ping 外部的一个节点三次:

# docker exec c1 ping -c 3 10.28.61.30
PING 10.28.61.30 (10.28.61.30) 56(84) bytes of data.
64 bytes from 10.28.61.30: icmp_seq=1 ttl=56 time=1.32 ms
64 bytes from 10.28.61.30: icmp_seq=2 ttl=56 time=1.30 ms
64 bytes from 10.28.61.30: icmp_seq=3 ttl=56 time=1.21 ms

--- 10.28.61.30 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.219/1.280/1.323/0.060 ms

1、start -> bridgecheck -> linker layer

和Container to Container的开端很类似,在bridge check后,数据流进入linker layer(docker0 is a bridge),并在该层进行iptables PREROUTING rules的处理,直到bridge decision之前:

TRACE: eb:broute:BROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:ff:27:17:4d proto = 0x0800 IP SRC=192.168.0.2 IP DST=10.28.61.30, IP tos=0x00, IP proto=1
TRACE: eb:nat:PREROUTING IN=veth0594f4b OUT= MAC source = 02:42:c0:a8:00:02 MAC dest = 02:42:ff:27:17:4d proto = 0x0800 IP SRC=192.168.0.2 IP DST=10.28.61.30, IP tos=0x00, IP proto=1
TRACE: raw:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: mangle:PREROUTING:policy:1 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: nat:PREROUTING:policy:2 IN=docker0 OUT= PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1

2、ebtable filter:INPUT -> routing decision -> iptables FORWARD

目的地址为外部host ip,需要三层介入转发,于是数据包经由eb:filter:INPUT向上走到达network layer的routing decision,根据路由表,将包转发到eth0:

TRACE: mangle:FORWARD:policy:1 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:1 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: filter:DOCKER-USER:return:1 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:2 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: filter:DOCKER-ISOLATION:return:1 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:5 IN=docker0 OUT=eth0 PHYSIN=veth0594f4b MAC=02:42:ff:27:17:4d:02:42:c0:a8:00:02:08:00 SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1

3、iptables nat:POSTROUTING match rule 1

由于要流出到主机外,因此在最后iptables nat:POSTROUTING中,数据包匹配到rule 1,即做MASQUERADE,将数据包源地址更换为host ip:10.171.77.0。

TRACE: mangle:POSTROUTING:policy:1 IN= OUT=eth0 PHYSIN=veth0594f4b SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1
TRACE: nat:POSTROUTING:rule:1 IN= OUT=eth0 PHYSIN=veth0594f4b SRC=192.168.0.2 DST=10.28.61.30 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=57351 DF PROTO=ICMP TYPE=8 CODE=0 ID=94 SEQ=1

4、iptables prerouting、forward、postrouting -> ebtabls output、postrouting

返回的应答由于IN设备为eth0,因此直接上到network layer进行iptable chain的处理。在路由后,OUT设备为docker0(bridge设备),因此在最后的环节需要下降到linker layer做output和postrouting处理:

TRACE: raw:PREROUTING:policy:2 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: mangle:PREROUTING:policy:1 IN=eth0 OUT= MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=10.171.77.0 LEN=84 TOS=0x00 PREC=0x00 TTL=57 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: mangle:FORWARD:policy:1 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:1 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: filter:DOCKER-USER:return:1 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:2 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: filter:DOCKER-ISOLATION:return:1 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: filter:FORWARD:rule:3 IN=eth0 OUT=docker0 MAC=00:16:3e:06:3a:3a:00:2a:6a:aa:12:7c:08:00 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: mangle:POSTROUTING:policy:1 IN= OUT=docker0 SRC=10.28.61.30 DST=192.168.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=56 ID=58706 PROTO=ICMP TYPE=0 CODE=0 ID=94 SEQ=1
TRACE: eb:nat:OUTPUT IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=10.28.61.30 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:filter:OUTPUT IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=10.28.61.30 IP DST=192.168.0.2, IP tos=0x00, IP proto=1
TRACE: eb:nat:POSTROUTING IN= OUT=veth0594f4b MAC source = 02:42:ff:27:17:4d MAC dest = 02:42:c0:a8:00:02 proto = 0x0800 IP SRC=10.28.61.30 IP DST=192.168.0.2, IP tos=0x00, IP proto=1

后续的请求和应答基本类似,少的还是nat PREROUTING和nat POSTROUTING,因为不再是NEW connection。

六、小结

个人赶脚:iptables的规则还是太复杂了,再加上bridge的ebtable规则,让人有些眼花缭乱。尤其是kube-proxy的规则又与docker的规则鞣合在一起,iptables的rules列表就显得更为冗长和复杂了。但目前kube-proxy稳定版依然以iptables为主要实现机制,不过kube-proxy对ipvs的支持也已经在路上了(kubernetes 1.8中ipvs处于alpha阶段),希望后续我们能有更多的选择。

此次实验全部日志内容参见:docker-bridge-network-demo-iptables-trace-log.txt文件。

Ubuntu下使用docker部署WordPress

上期docker使用教程

http://www.jianshu.com/p/46c7bed70f75

感觉中了docker的毒,简直神器,这次带来部署WordPress的教程

一、pull MySQL镜像

sudo docker pull mysql

二、运行MySQL

docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql

三、pull WordPress镜像

sudo docker pull wordpress

四、运行WordPress

sudo docker run --name wordpress --link mysql:mysql -p 80:80 -d wordpress

五、浏览器访问

是不是很熟悉的界面

未分类

将 Docker Registry 设置为 Raspberry Pi 上的拉取式缓存

如果您的网速缓慢且有多个用户在下载 Docker 映像,为什么不使用它作为 Raspberry Pi 上的缓存呢?这样,在使用 Docker 时可以节省时间和带宽。Raspberry Pi 非常有用,它便携、经济且耗电量微乎其微。通过创建本教程中介绍的解决方案,每次下载映像时,它都会缓存在 Pi 上;然后对于所有后续请求,将从本地缓存提供同一个映像。

Docker 是一个流行的 DevOps 工具,它为开发人员和系统管理员提供了一个平台,使他们能构建、发布并在任何地方运行应用程序。这意味着,开发人员能在本地机器上构建 Docker 映像,将它们发布到注册表,然后在云服务器上运行它们。注册表之于 Docker 映像,就像 Git 之于编码。

“本教程中的设置有助于节省时间和带宽。Raspberry Pi 非常有用,它便携、经济且耗电量微乎其微。”

Docker Hub 是一个流行的注册表,它允许用户免费托管映像。如果您的开发小组在同一个网速缓慢或带宽有限的地方协同使用 Docker,而且所有人都从 Docker Hub 下载映像,您可能会面临一些性能挑战。可以将本地网络上的私有 Docker Registry 设置为拉取式缓存,以便每次您下载一个映像时都会缓存它,对于后续的每个请求,将从本地缓存提供该映像。这也可以节省宝贵的带宽和大量时间。例如,代理缓存的基于 Ubuntu 的映像可能被多次下载到不同机器上,但该映像仅从 Docker Hub 抓取一次。

在本教程中,您将使用 Raspberry Pi 作为 Docker Hub 的拉取式缓存。Raspberry Pi 是一种信用卡大小的小型计算机,它使用的电量非常少,完整 PC 能处理的许多工作它都能处理。我们将演示从头设置 Raspberry Pi,将 Golang 安装在 Raspberry Pi 上,以及编译 Go 二进制程序(因为 Docker Registry 是使用跨平台语言 Golang 编写的)。如果一切顺利,只需两小时即可完成所有设置。我们首先看看一些前提条件。

需要的硬件和软件

开始之前,确保您拥有以下硬件:

  • Raspberry 第 2 版或更高版本
  • 8GB 或更大的 SD 卡
  • 带以太网端口的 Web 路由器
  • LAN 线
  • 安装了 Docker 的机器

还需要安装以下软件:

  • 基本 Linux 命令行
  • Docker(在这里获取最新版本)

设置

首先设置 Raspberry Pi。如果有一个运行正常的 Raspberry Pi,可以跳过这一步。Raspberry Pi 有许多发行版,但本示例使用 Raspbian,这是 Raspberry Pi 网站上提供的官方发行版。

下载 Raspbian

打开一个新终端窗口,使用 wget 下载 Raspbian 映像,该工具已预先安装在大部分 Linux 发行版上:

$ mkdir -p ~/rpi-registry
$ cd ~/rpi-registry
$ wget https://downloads.raspberrypi.org/raspbian_lite/images/
raspbian_lite-2017-04-10/2017-04-10-raspbian-jessie-lite.zip

这会将“~/rpi-registry”目录内的发行版映像下载到您的机器上。通过执行以下命令,使用 SHA 校验和与上游的 SHA 来验证下载的映像的完整性:

$ shasum 2017-04-10-raspbian-jessie-lite.zip
c24a4c7dd1a5957f303193fee712d0d2c0c6372d  
2017-04-10-raspbian-jessie-lite.zip

如果 SHA 校验和不匹配,则必须重新下载该映像。如果网速缓慢,在下载时应该使用 torrent 选项。

准备 SD 卡

要将 Raspbian 安装在 SD 卡上,可以使用 Etcher 工具,该工具可从 https://etcher.io 下载。Etcher 是一个将映像刻录到 SD 卡和 USB 驱动器上的应用程序。它可用于 Windows、Linux 和 Mac OS X。将 SD 卡插入笔记本电脑或台式机中,打开 Etcher。然后选择下载的 Raspbian 映像,确保它检测到您的 SD 卡后,单击Flash! (参见图 1)。将映像刻录到 SD 卡上需要花几分钟。

图 1. Etcher 将映像刻录到 SD 卡上
未分类

在这里设置无头 Pi,这意味着不会向 Raspberry Pi 连接键盘或显示器,所以需要在首次引导时启用 SSH。必须这么做,因为最新的 Raspbian 构建版默认已禁用 SSH。为此,在首次引导之前,在 SD 卡的引导分区中创建一个名为“SSH”的文件。该文件不得有任何扩展名,可以是空的,也可以是一个文本文件。
为此,可以在 File Explorer 中或从终端打开 SD 卡。在 Mac OS X 上,安装路径为 /Volumes/boot:

$ cd /Volumes/boot
$ touch ssh
$ ls -la
...
-rwxrwxrwx  1 kn330  staff  4235224 Mar  2 16:52 kernel7.img
drwxrwxrwx  1 kn330  staff     8704 Apr 10 09:57 overlays
-rwxrwxrwx  1 kn330  staff        0 Jun 11 03:04 ssh
-rwxrwxrwx  1 kn330  staff  2848068 Apr  7 14:39 start.elf
...

SD 卡现在已准备好引导。将卡插入 Raspberry Pi 中并将它连接到家庭路由器。

SSH 登录

要登录,需要 Raspberry Pi 的 IP 地址,可以在路由器的管理页面上找到该信息。大多数路由的管理页面地址为 192.168.1.1,但您的可能不同,所以应手动查找您的路由器(参见图 2)。也可以使用 Nmap 等网络扫描工具找到此信息。

图 2. 您的路由器包含 Pi 的 IP 地址
未分类

拥有 IP 地址后,就可以使用 Secure Shell (SSH) 登录了,输入 pi 作为用户名和 raspberry 作为密码,这是使用 Pi 随带的 Raspbian 操作系统的默认用户名和密码:

$ ssh [email protected]
[email protected]'s password: 
The programs included with the Debian GNU/Linux system are free
software; the exact distribution terms for each program are described
in the individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
SSH is enabled and the default password for the 'pi' user has not
been changed.
This is a security risk - please login as the 'pi' user and type
'passwd' to set a new password.
pi@raspberrypi:~ $

Raspbian 现在已在运行,所以您可以继续设置 Docker Registry。请注意,分配给 Pi 的 IP 地址可能更改,所以您可能需要向它分配一个静态 IP。

代理:分发 Docker 映像

Docker Registry 是一个无状态、容易扩展的服务器端应用程序,它存储并允许您分发 Docker 映像。Docker Hub 提供了一个可免费使用的托管注册表,这是所有 Docker 客户端的默认注册表,所以在执行 docker pull ubuntu 时,它会从 Docker Hub 抓取映像。可以运行一个注册表缓存来将这些映像存储在本地,而不是经常从互联网下载它们。为此,可以使用 Docker Registry 为您的家庭或组织自行托管一个私有注册表。这个开源应用程序是使用 Golang 编写的,可在 Github 上获得。可以将 Docker Registry 作为 Docker 容器运行,但我们将从头开始构建它并在裸机 Raspberry Pi 上执行该二进制程序。

工作原理

任何 Docker 客户端第一次拉入映像时,它都会从 Docker Hub 抓取映像,并在文件系统上存储一个副本。对于任何后续请求,将从本地注册表而不是 Docker Hub 提供副本。它非常聪明,在将映像提供给客户端之前,会检测上游对映像的任何更改并更新映像(参见图 3)。

图 3. docker_proxy 检测任何更改
未分类

操作说明

Golang 允许对多个架构执行交叉编译,这意味着可以在笔记本或台式机上执行以下步骤,构建 Docker Registry 的二进制程序并将它们复制到 Raspberry Pi。出于简单性考虑,我们将介绍如何在 Raspberry Pi 自身上执行构建流程。

首先安装 Golang。

安装 Golang

安装 Go 的最容易方式是使用 gvm (Golang Version Manager),该工具类似于用于 Ruby 的 rvm 或用于 Python 应用程序的 virtualenv。

pi@raspberrypi:~ $ sudo apt-get update   pi@raspberrypi:~ $ sudo
 apt-get install -y curl git mercurial make  binutils bison gcc 
build-essential pi@raspberrypi:~ $ bash < <(curl -s -S -L
 https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/
gvm-installer) pi@raspberrypi:~ $ source /home/pi/.gvm/scripts/gvm

您可能希望将源代码 /home/pi/.gvm/scripts/gvm 添加到 ~/.bashrc 文件中,以避免每次登录时都获取它:

pi@raspberrypi:~ $ go install go1.4
pi@raspberrypi:~ $ go install go1.8
pi@raspberrypi:~ $ gvm use go1.8 [--default]
pi@raspberrypi:~ $ go -v
pi@raspberrypi:~ $ go version
 go version go1.4 linux/arm

备注:Go 1.5 C 无法用于编译,所以在安装 Go 1.8 之前,需要下载 Go 1.4。

创建注册表二进制程序

Docker Registry 存储库提供了一个 Makefile,用于构建这些二进制程序并将它们放在 bin 目录中。Registry 包含在 Docker 发行版存储库中,所以可以克隆它:

pi@raspberrypi:~ $ git clone https://github.com/docker/distribution.git
pi@raspberrypi:~ $ cd distribution

接下来,安装依赖项:

pi@raspberrypi:~ $ go get ./...

创建二进制程序:

pi@raspberrypi:~ $ GOOS=linux GOARCH=arm make binaries

设置 GOOS 和 GOARCH 环境变量,让编译器知道您正在 Linux 操作系统上针对 ARM 架构而构建。如果 make 命令成功运行,将会在 bin/ 目录中看到这些二进制程序。

pi@raspberrypi:~/distribution $ ls bin/
digest  registry  registry-api-descriptor-template

测试二进制包

需要一个配置文件才能运行该注册表。创建 ~/registry-config.yml 并将以下内容复制到其中:

version: 0.1
storage: 
  cache: 
    blobdescriptor: inmemory
  filesystem: 
    rootdirectory: /var/lib/registry
http: 
  addr: :5000

您现在已拥有运行注册表所需的所有资源,接下来运行您自己的私有注册表:

pi@raspberrypi:~ $ sudo mkdir -p /var/lib/registry
pi@raspberrypi:~ $ sudo chown $USER /var/lib/registry
pi@raspberrypi:~/distribution $ ~/distribution/bin/registry
 serve ~/registry-config.yml
WARN[0000] No HTTP secret provided - generated random secret. 
如果负载平衡器背后有多个注册表,这可能导致上传问题。要提供共享密钥,可将 http.secret 填入配置文件中,或者设置 
REGISTRY_HTTP_SECRET 环境变量。go.version=go1.8
 instance.id=a80670e6-d840-44f3-aefa-9f6863fb6a6e
 version="v2.6.0+unknown"INFO[0000] redis not configured
 go.version=go1.8 instance.id=a80670e6-d840-44f3-aefa-
9f6863fb6a6e version="v2.6.0+unknown"
INFO[0000] Starting upload purge in 10m0s
go.version=go1.8 instance.id=a80670e6-d840-44f3-aefa-9f6863fb6a6e
 version="v2.6.0+unknown"
INFO[0000] using inmemory blob descriptor cache
go.version=go1.8 instance.id=a80670e6-d840-44f3-aefa-9f6863fb6a6e
 version="v2.6.0+unknown"
INFO[0000] Starting cached object TTL expiration scheduler...
go.version=go1.8 instance.id=a80670e6-d840-44f3-aefa-9f6863fb6a6e
 version="v2.6.0+unknown"
INFO[0001] Discovered token authentication URL:
https://auth.docker.io/token  go.version=go1.8 instance.id=
a80670e6-d840-44f3-aefa-9f6863fb6a6e
INFO[0001] Registry configured as a proxy cache to 
https://registry-1.docker.io  go.version=go1.8 instance.id=
a80670e6-d840-44f3-aefa-9f6863fb6a6e version="v2.6.0+unknown"
INFO[0001] listening on [::]:5000
go.version=go1.8 instance.id=a80670e6-d840-44f3-aefa-9f6863fb6a6e
version="v2.6.0+unknown"

瞧!您自己的私有注册表正在您自己的 Raspberry Pi 上正常运行。您可能看到一些警告消息,但暂时可以安全地忽略它们。我们也检查一下来自笔记本电脑的连接:

local-osx $  curl -I 192.168.1.2:5000/v2/
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Date: Sun, 11 Jun 2017 13:26:18 GMT

响应代码 200 表明运行正常。

代理设置

我们测试了我们的注册表,它运行正常,但还无法执行我们希望它执行的操作。我们的目的是设置一个代理,以在首次下载时缓存映像,然后在本地为后续请求提供它们。这可以通过向配置文件中添加一个代理节来完成:

version: 0.1
storage: 
  cache: 
    blobdescriptor: inmemory
  filesystem: 
    rootdirectory: /var/lib/registry
http: 
  addr: :5000
proxy: 
 remoteurl: https://registry-1.docker.io

在这里,remoteurl 是 Docker Hub 注册表的 URL。如果愿意,也可以将端口从 5000 更改为任何其他端口。使用同一个命令运行该注册表:

pi@raspberrypi:~/distribution $ ~/distribution/bin/registry serve
 ~/registry-config.yml &

请注意末尾的 &,它使该进程在后台运行。现在可以在笔记本电脑上配置 Docker 守护进程,以使用这个注册表镜像(参见图 4)。如果使用 Docker for Mac,可通过转到 Preferences > Registry mirrors 并添加 http://192.168.1.2:5000,然后重新启动 Docker 来实现此目的。如果在任何其他操作系统上使用 Docker,可以按照相关文档(参阅“相关主题”)更新 Docker 守护进程首选项。

图 4. Docker 守护进程配置
未分类

现在,每次您在笔记本电脑上下载映像,它都会缓存在 Raspberry Pi 上的 /var/lib/registry 目录中。您可能希望在该路径挂载一个外部驱动器。我们通过拉取 Mongo Docker 映像来检验此做法:

local-osx $ time docker pull mongo
Using default tag: latest
latest: Pulling from library/mongo
56c7afbcb0f1: Pull complete
...
e233325a655e: Pull complete
Digest:
sha256:c4bc4644b967a4b58022a79cf5c9afcd25ed08180c958a74df57b7753cfc8649
Status: Downloaded newer image for mongo:latest
docker pull mongo  0.15s user 0.18s system 0% cpu 2:22.28 total

local-osx $ docker rmi mongo

local-osx $ time docker pull mongo
Using default tag: latest
latest: Pulling from library/mongo
56c7afbcb0f1: Pull complete
...
e233325a655e: Pull complete
Digest: sha256:c4bc4644b967a4b58022a79cf5c9afcd25ed08180c958a74df57b7753cfc8649
Status: Downloaded newer image for mongo:latest
docker pull mongo  0.15s user 0.13s system 0% cpu 51.303 total

可以看到,第一次拉取花了 142 秒,而第二次在 60 秒内就完成了。

结束语

如果您的网络连接缓慢且有多个用户在下载 Docker 映像,本文中介绍的设置可以帮助您节省时间和带宽。Raspberry Pi 非常有用,它便携、经济且耗电量微乎其微。您已看到使用 Raspberry Pi 作为 Docker Hub 的拉取式缓存的好处:每次您下载一个映像,它都会缓存在 Pi 上,对于每个后续请求,会从本地缓存中提供该映像。

使用 HAProxy + Keepalived 构建基于 Docker 的高可用负载均衡服务(二)

本文主要介绍把 HAProxy + Keepalived 服务组合形成单独的镜像,并能够自定义配置项。适合具有 Docker 和 Bash 相关基础的开发、运维等同学。

使用 Docker 组合构建 haproxy-keepalived 服务

未分类

上图为我们要构建的 haproxy-keepalived 服务的工作原理图,原理表述如下:

START:

  • 根据 Bash 脚本和 Docker 容器的环境变量生成 HAProxy 和 Keepalived 的配置文件
  • 在容器内部启动 HAProxy 和 Keepalived 服务

STOP:

  • ENTRYPOINT 使用 trap 接收到传递来的 SIGTERM 信号
  • 使用 kill -ITEM $pid 的方式,结束掉 HAProxy & Keepalived 两个服务。

下边会按照上述步骤介绍如何工作。

生成 HAProxy & Keepalived 配置文件

haproxy_cfg_init.sh
#!/bin/bash

HAPROXY_DIR="/usr/local/etc/haproxy"
TMP_HAPROXY="/tmp/haproxy"

TMP_FILE="${TMP_HAPROXY}/tmp.cfg"
TMP_TEMPLATE="${TMP_HAPROXY}/template.cfg"

TEMPLATE_FILE="/haproxy/template.cfg"
HAPROXY_CFG="${HAPROXY_DIR}/haproxy.cfg"

# mkdir to prevent not exists
mkdir -p ${HAPROXY_DIR}
mkdir -p ${TMP_HAPROXY}
rm -rf ${HAPROXY_DIR}/haproxy.cfg

# delete old haproxy.cfg and touch tmp.cfg
rm -rf ${HAPROXY_CFG}
rm -rf ${TMP_FILE}
touch ${TMP_FILE}

# copy template.cfg to tmp dir
cp ${TEMPLATE_FILE} ${TMP_TEMPLATE}

COUNTER=1
while [ ${COUNTER} -gt 0 ]
do
    haproxy_item=$(printenv haproxy_item${COUNTER})

    if [ -z "${haproxy_item}" ]
    then
        break;
    fi

    # echo haproxy_item to /tmp/haproxy/tmp.cfg
    echo "${haproxy_item${COUNTER}}" >> ${TMP_FILE}

    let COUNTER+=1
done
echo "Success generate tmp.cfg!"

sed -i "/#CUSTOM/r ${TMP_FILE}" ${TMP_TEMPLATE}
echo "Success generate template.cfg"

touch ${HAPROXY_CFG}
envsubst < ${TMP_TEMPLATE} > ${HAPROXY_CFG}

echo "Success generate haproxy.cfg, file content is below:"
echo "----------------haproxy.cfg------------------"
cat ${HAPROXY_CFG}
echo "------------------ End ----------------------"

容器在启动的时候,传入 haproxy_item_${CURSOR} 环境变量,上述脚本根据传入的环境变量与 template.cfg 文件结合,生成最终的 /usr/local/etc/haproxy/haproxy.cfg 配置文件。

大致相似的原理,根据 init_keepalived_conf.sh 脚本,与传入的环境变量结合,使用环境变量替换 keepalived_template.conf 文件中的环境变量,来生成 最终的 /etc/keepalived/keepalived.conf 配置文件。

#!/bin/bash
set -e

TEMPLATE="/keepalived/keepalived_template.conf"
TMP_TEMPLATE="/tmp/keepalived/tmp_keepalived_template.conf"
KEEPALIVED_FILE="/etc/keepalived/keepalived.conf"

# mkdir to prevent not exist
mkdir -p /tmp/keepalived/
mkdir -p /etc/keepalived/

# copy template to /tmp/keepalived/tmp_keepalived_template.conf
cp ${TEMPLATE} ${TMP_TEMPLATE}

envsubst < ${TMP_TEMPLATE} > ${KEEPALIVED_FILE}
echo "Success generate ${KEEPALIVED_FILE}"
echo "----------------------keepalived.conf----------------------"
cat ${KEEPALIVED_FILE}
echo "--------------------------- End ---------------------------"

容器内部启动 HAProxy & Keepalived 服务

查看 Dockerfile 文件,这个 haproxy-keepalived 镜像是基于 haproxy:1.7.9 基础镜像来做的修改。为了符合一个容器启动 HAProxy & Keepalived 两个服务的预期,我们增加了一个 docker-entrypoint-override.sh 的脚本来作为 haproxy-keepalived 镜像的 ENTRYPOINT。

在 docker-entrypoint-override.sh 中,我们分别用如下两条命令启动来启动两个服务:

  • HAProxy: exec /docker-entrypoint.sh “$@” &

    直接后台运行 haproxy:1.7.9 官方镜像的原 docker-entrypoint.sh 来启动 haproxy

  • Keepalived: exec /start_keepalived.sh “$@” &

    后台运行 start_keepalived.sh 脚本来启动 Keepalived 服务

接下来会稍微详细说一下 start_keepalived.sh 脚本:

#!/bin/bash

LANUCH_SCRIPT="keepalived --dont-fork --dump-conf --log-console --log-detail --log-facility 7 --vrrp -f /etc/keepalived/keepalived.conf"

eval $LANUCH_SCRIPT
while true; do
  k_pid=$(pidof keepalived)
  if [ -n "$k_id" ]; then
    break;
  fi

  kill -TERM $(cat /var/run/vrrp.pid)
  kill -TERM $(cat /var/run/keepalived.pid)
  echo "ERROR: Keepalived start failed, attempting restart ."
  eval $LANUCH_SCRIPT
done

echo "Keepalived started successfuly!"

在上述脚本中,我们在运行 LANUCH_SCRIPT 后,又做了一个循环判断。原因是在我们使用这个自定义的 haproxy-keepalived 服务的时候,在重启容器后 Keepalived 并不能正常启动,会有个报错:

Keepalived: Daemon is already running

但实际上,这个 Daemon 我们已经给它 kill 掉了。我们这里再执行一次判断,如果 $k_id 值不为空,则 Keepalived 启动成功;如果 $k_id 值为空,我们会再 kill 一次 Keepalived 的进程,然后重启 Keepalived。

容器停止,Graceful Shutdown

在容器停止的时候,需要做 Graceful Shutdown 的操作,在这里的目标就是:容器关闭前干掉 HAProxy & Keepalived 的进程。

仍然回到 docker-entrypoint-override.sh 脚本,在脚本中我们定义了接收 SIGITERM 信号的处理事件:

trap "stop; exit 0;" SIGTERM SIGINT

# handler for SIGINT & SIGITEM
stop() {
  echo "SIGTERM caught, terminating <haproxy & keepalived> process..."

  # terminate haproxy
  h_pid=$(pidof haproxy)

  kill -TERM $h_pid > /dev/null 2>&1
  echo "HAProxy had terminated."

  # terminate keepalived
  kill -TERM $(cat /var/run/vrrp.pid)
  kill -TERM $(cat /var/run/keepalived.pid)
  echo "Keepalived had terminated."

  echo "haproxy-keepalived service instance is successfuly terminated!"
}

当容器被 stop 或者 kill 的时候,会触发这个 handler 事件。我们在事件中对两个服务做了下简单的处理。

如何使用

首先,准备两台 Linux 虚拟机,假定 IP 分别为:192.168.0.41、192.168.0.42,并找到一个 VIP,假定为 192.168.0.40。

在 192.168.0.41 机器上新建 /data/haproxy-keepalived/docker-compose.yml 文件:

root@haproxy-master:/data/haproxy-keepalived# cat docker-compose.yml
version: '3'
services:

  haproxy-keepalived:
    image: "pelin/haproxy-keepalived"
    privileged: true
    network_mode: host
    restart: always
    environment:
      KEEPALIVED_STATE: "MASTER"
      KEEPALIVED_INTERFACE: "ens18"
      KEEPALIVED_PRIORITY: "105"
      KEEPALIVED_V_ROUTER_ID: "40"
      KEEPALIVED_VIP: "192.168.0.40"
      haproxy_item1: |-
        listen app-1
            bind *:4000
            mode http
            maxconn 300
            balance roundrobin
            server server1 192.168.0.21:4001 maxconn 300 check
            server server2 192.168.0.22:4002 maxconn 300 check
         listen app-2
            bind *:5000
            mode http
            maxconn 300
            balance roundrobin
            server server1 192.168.0.21:5001 maxconn 300 check
            server server2 192.168.0.22:5002 maxconn 300 check

注意上述配置:
KEEPALIVED_STATE             # Keepalived 节点初始状态
KEEPALIVED_INTERFACE         # Keepalived 绑定的网卡
KEEPALIVED_PRIORITY          # 权重
KEEPALIVED_V_ROUTER_ID       # router_id
KEEPALIVED_VIP               # 虚拟 IP

其中,KEEPALIVED_INTERFACE 需要根据实际情况而定。比如笔者用的 Ubuntu 16.04 的主网卡是 ens18,Ubuntu 14.04 的网卡是 eth0。

然后使用如下命令开启服务:

root@haproxy-keepalived:/data/haproxy-keepalived# docker-compose up

在启动后,访问浏览器:192.168.0.41:1080/stats 即可看到有界面出现

同理,在 42 机器上做上述操作,其中 KEEPALIVED_STATE 需要修改为 BACKUP

如何扩展

目前实现的功能:

  • 根据环境变量动态调整 HAProxy & Keepalived 配置
  • 容器 Graceful Shutdown
  • 容器能够正常 Restart,Crashed 后能正常自动重启

TODO:

  • 直接 Bind 配置文件进容器

如果需要自定义一个 haproxy-keepalived 镜像的话,可以参考上边的思路来做。当然,如果场景可以使用 Bind 的方式,那自然是更简单了。

如果可以直接 Bind 配置文件进容器,上述很多脚本都可以省略掉。但是,我们更寄期望于在容器编排工具中,使用环境变量的方式来初始化配置文件。为什么有这样的考虑?

  • 当有多台机器的时候,我们需要一台一台去修改 Bind 的配置文件。虽然可以使用 Ansible 之类的自动化机制来做,但是在这种场景下把配置和服务分离并不太合适;
  • 对以后做服务的自动伸缩机制友好。服务在伸缩之后,通过服务发现我们需要更新我们的 LB 的配置。如何更新?自然是通过我们的中心容器编排工具来自动更新。去触发脚本更新与我们的期望不符。

LICENSE

Source code: https://coding.net/u/Pelin_li/p/haproxy-keepalived/git

Based on MIT LICENSE: https://coding.net/u/Pelin_li/p/haproxy-keepalived/git/blob/master/LICENSE

使用 Docker 完成 MySQL 数据库主从配置

使用 docker 进行数据库主从配置,因为我有这个需求,而在网上搜索后发现没有满足我需求的相关实践文档,有的是一些零零碎碎的文档,而且在参照这些文档进行部署的时候我还踩了许多坑。

因此根据我自己部署成功的经验,我写了这个文档。

使用 docker 自然就需要有 docker 环境,当然 docker 的镜像在国内访问比较慢,建议使用国内的源。

构建 DockerFile

我们的工作是在 Mysql 镜像基础上进行的。

docker pull mysql:5.7.20 这条命令会下载最新的 mysql 镜像,当然也可以指定版本。

新建一个 DockerFile 文件:

FROM mysql:5.7.20

EXPOSE 3306

COPY my.cnf /etc/mysql/

CMD ["mysqld"]

在构建镜像前,我们需要 mysql 的配置文件 my.cnf ,可以先启动一个 mysql 的容器,然后进入容器复制它下来,并对其进行修改:

!includedir /etc/mysql/conf.d/
[mysqld]
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
#log-error=/var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address   = 127.0.0.1
log-bin=/var/log/mysql/mysql-bin.index
server-id=1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

以上是我修改好的一个 my.conf 文件,我们主要修改的是以下这些选项:

#bind-address   = 127.0.0.1 # 注释这个选项可以让远程机器访问,或者可以修改为 0.0.0.0
log-bin=/var/log/mysql/mysql-bin.index # 开启 log-bin 日志,日志路径
server-id=1 # 服务器唯一ID,默认是1,一般取IP最后一段,这里看情况分配

这是我的主库的 my.cnf,从库的基本一样,只是 servier-id 不一样,从库为 2,将这个文件也放在文件夹里。

构建需要的文件结构就是以下这样:

master
├── Dockerfile
└── my.cnf
slave
├── Dockerfile
└── my.cnf

然后在master目录中使用 docker build -t master/mysql:5.7.20 . 命令构建镜像主库,从库构建命令 docker build -t slave/mysql:5.7.20 .。命令最后有个.,不要忘记,代表当前目录。-t 的意思是tag,是 –tag 的缩写,也就是命名这个镜像,如果不加docker会随机给这个镜像一个名字,建议格式为name:tag

构建完成后我们使用以下命令启动主库和从库:

docker run -p 3306 --name mysql-master -e MYSQL_ROOT_PASSWORD=root -d master/mysql:5.7.20

docker run -p 3306 --name mysql-slave --link mysql-master:master -e MYSQL_ROOT_PASSWORD=root -d slave/mysql:5.7.20

然后分别执行docker exec -it mysql-master bash和docker exec -it slave-master bash命令进入到容器内部。执行mysql -uroot -proot进入mysql环境,这时我们的环境搭建就已经完成了,下面正式配置主从连接。

当然此时docker会分配一个唯一的端口给容器,我们也可以用mysql客户端连接进行配置。使用docker ps查看端口号,就可以用客户端连接进行管理。

☁  mysql-master-slave  docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                     NAMES
6c7648e829e0        slave/mysql:5.7.20    "docker-entrypoint..."   4 minutes ago       Up 4 minutes        0.0.0.0:32769->3306/tcp   mysql-slave
483842c63235        master/mysql:5.7.20   "docker-entrypoint..."   5 minutes ago       Up 5 minutes        0.0.0.0:32768->3306/tcp   mysql-master

使用GRANT REPLICATION SLAVE ON . to ‘user’@’%’ identified by ‘mysql’; 或者 GRANT REPLICATION SLAVE ON . TO ‘user’@’192.168.1.200’ IDENTIFIED BY ‘mysql’;创建一个用户,上一个是所有ip都可以访问,下一个是指定ip才可以访问。

然后使用GRANT SELECT,REPLICATION SLAVE ON . TO ‘user’@’%’;给这个用户读取权限。

使用show master status查看mysql主容器的状态,打印如下信息:

+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 |      154 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+

使用hostname查看主容器的hostname,如我的主容器是483842c63235。

接下来配置从库mysql:

change master to
master_host='master',#要连接的主服务器的ip
master_user='user',#指定的用户名,最好不要用root
master_log_file='mysql-bin.000003',#主库记录的值
master_log_pos=154,#主库的pos值
master_port=3306,#主库3306映射的端口
master_password='mysql';#主库要连接的用户的密码了

使用start slave启动主从同步

使用命令查看show slave statusG

打印如下信息:

*************************** 1. row ***************************
               Slave_IO_State: Connecting to master
                  Master_Host: master //主服务器地址
                  Master_User: user //授权帐户名,尽量避免使用root
                  Master_Port: 3306 //数据库端口,部分版本没有此行
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003 //同步主库的日志文件名
          Read_Master_Log_Pos: 154 //同步读取二进制日志的位置,大于等于
               Relay_Log_File: 6c7648e829e0-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes //此状态必须YES
            Slave_SQL_Running: Yes //此状态必须YES
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 154
              Relay_Log_Space: 154
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 0
                  Master_UUID:
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
         Replicate_Rewrite_DB:
                 Channel_Name:
           Master_TLS_Version:
1 row in set (0.00 sec)

这样就是完成了一组主从mysql的配置。

shell脚本

一下一个相应的shell脚本,可以在1分钟能启动一组mysql主从容器,运行这个脚本需要稍作修改,主要是mysql配置文件。

#!/bin/bash

MASTER_DIR=/var/lib/mysql/mysql-master
SLAVE_DIR=/var/lib/mysql/mysql-slave

## First we could rm the existed container
docker rm -f mysql-master
docker rm -f mysql-slave

## Rm the existed directory
rm -rf $MASTER_DIR
rm -rf $SLAVE_DIR

## Start instance
docker run -p 3306 --name mysql-master  -v /etc/master.cnf:/etc/mysql/my.cnf -v $MASTER_DIR:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d master/mysql:5.7.20
docker run -p 3306 --name mysql-slave  -v /etc/slave.cnf:/etc/mysql/my.cnf -v $MASTER_DIR:/var/lib/mysql --link mysql-master:master -e MYSQL_ROOT_PASSWORD=root -d slave/mysql:5.7.20

## Creating a User for Replication
docker stop mysql-master mysql-slave
docker start mysql-master mysql-slave

sleep 3

docker exec -it mysql-master mysql -S /var/lib/mysql/mysql.sock -e "CREATE USER 'users'@'127.0.0.1' IDENTIFIED BY 'mysql';GRANT REPLICATION SLAVE ON *.* TO 'users'@'127.0.0.1';"

## Obtaining the Replication Master Binary Log Coordinates
master_status=`docker exec -it master mysql -S /var/lib/mysql/mysql.sock -e "show master statusG"`
master_log_file=`echo "$master_status" | awk  'NR==2{print substr($2,1,length($2)-1)}'`
master_log_pos=`echo "$master_status" | awk 'NR==3{print $2}'`
master_log_file="'""$master_log_file""'"

## Setting Up Replication Slaves 
docker exec -it mysql-slave mysql -S /var/lib/mysql/mysql.sock -e "CHANGE MASTER TO MASTER_HOST='master',MASTER_PORT=3306,MASTER_USER='users',MASTER_PASSWORD='mysql',MASTER_LOG_FILE=$master_log_file,MASTER_LOG_POS=$master_log_pos;"
docker exec -it mysql-slave mysql -S /var/lib/mysql/mysql.sock -e "start slave;"
docker exec -it mysql-slave mysql -S /var/lib/mysql/mysql.sock -e "show slave statusG"

## Creates shortcuts
grep "alias mysql-master" /etc/profile
if [ $? -eq 1 ];then
    echo 'alias mysql="docker exec -it mysql-master mysql"' >> /etc/profile
    echo 'alias master="docker exec -it mysql-master mysql -h 127.0.0.1 -P3306"' >> /etc/profile
    echo 'alias slave="docker exec -it mysql-master mysql -h 127.0.0.1 -P3307"' >> /etc/profile
    source /etc/profile
fi

Centos7.4下用Docker-Compose部署WordPress

前言

最近在学习Docker相关知识,通过阅读第一本Docker书后,正想着手实战用一下这个技术,但又不太敢直接在项目环境下动手。考虑足足三秒钟之后决定买个阿里云ECS搭建一个属于自己的基于Docker的WordPress博客Daniel Fu’s hut传送门。

本博客搭建环境(阿里云ECS的购买与基本的安全组配置等工作在文中省略,各位看官可自行研究):

  • 阿里云ECS
  • Centos 7.4

部署工具

  • Docker
  • Docker Compose(Compose工具比起单纯的Dockerfile来更为便利、更易管理)
  • WordPress和MySql5.7(运行在Docker容器中)

现在逐个讲解下如何安装:

Docker:

根据官方文档,Docker分为Community Edition (CE)和Enterprise Edition (EE)两个版本,我们作为学习和个人使用,当然选择的是Community Edition (CE),安装步骤如下:

// 步骤1 - 为了确保没有安装过老的Docker版本,先将已经安装的Docker从宿主机上删除(如果是在使用中的正式服务器,请谨慎执行此步):
$ sudo yum remove docker 
  docker-common 
  docker-selinux 
  docker-engine

// 步骤2 - 安装Docker所需的包:
$ sudo yum install -y yum-utils 
  device-mapper-persistent-data 
  lvm2

// 步骤3 - 配置到稳定的Docker CE安装库:
$ sudo yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo

// 步骤4 - 安装Docker CE:
$ sudo yum install docker-ce

// 步骤5 - 启动Docker服务:
$ sudo systemctl start docker

// 步骤6 - 测试是否安装成功:
// 可以通过查看版本的形式确认安装是否成功:
$ docker --version 
// 也可以通过直接运行hello-world容器来确认安装是否成功:
$ docker run hello-world

Docker Compose:

为了便于使用,我们需要安装Docker Compose来管理和运行Docker容器,Docker Compose的安装步骤如下:

// 步骤1 - 下载安装文件:
$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

// 步骤2 - 给已下载的安装文件添加执行权限:
$ sudo chmod +x /usr/local/bin/docker-compose

// 步骤3 - 测试是否安装成功:
$ docker-compose --version

部署WordPress和MySql容器:

先创建一个工作目录,并创建名为docker-compose.yml的文件:

$ cd /usr/
$ sudo mkdir myblog && cd myblog
$ sudo vim docker-compose.yml

将如下内容保存在docker-compose.yml文件中:

version: '3'
services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: your-mysql-root-password
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress
   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     volumes:
        - wp_site:/var/www/html
     ports:
       - "80:80"
       - "443:443"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:
    wp_site:
  • 关于Composer所使用的的yml文件的语法,请参考官方文档,这里就不一一解释了(因为我自己也是刚入门,只会几个基本的用法)。

  • 和官方给的Demo不同,上述yml文件中,我在volumes中添加了wp_site的卷,并将其挂在到wordpress容器中,这样,当容器被停止或者删除后,重新安装并启动wordpress容器时,已安装的plugins也可以直接继续使用,而不是重新安装。

此时,我们直接使用docker-compose命令启动容器:

$ sudo docker-compose up -d

启动之后,我们就可以通过 http://ecs-ip (因为我们绑定的是宿主机的80端口) 来访问WordPress(如果不能访问,请查看阿里云安全组中,是否已经开启了你所指定端口的公网访问权限,一般80端口是默认开启的)。

如果需要关闭服务,则执行如下命令:

$ sudo docker-compose down

开启你的博客之旅:

我们的WordPress博客已经搭建完成,通过访问后台管理页面来初始化网站。然后找一个喜欢的主题,安装好必要的插件,我们就可以在一个属于自己的博客网站上写下自己的第一篇博客。

分享一下:这是我第一次用WordPress,还有很多东西需要慢慢摸索。目前我安装了Jetpack插件和WP Editor.MD插件,选用的主题是Sirius免费版。

遇到的挫折:

第一次安装时,当使用 sudo docker-compose up -d 命令启动容器后,提示容器启动正常,但是无法访问网站,此时通过sudo docker logs xxx_wordpress_1(xxx_wordpress_1以实际生产的容器名称为准)查看日志,看到日志中不断提示数据库无法连接的错误。花了好几个小时排查,总算找到问题所在,是因为以前在机器上写Docker相关的测试例子的时候,宿主机上设置了几个全局变量(如:ServerName、ServerHost、UserName等,可通过env命令查看当前系统环境变量),这些全局变量导致了wordpress容器无法连接到db容器。这个小插曲告诉我们,不要随便在宿主机上设置环境变量,如果必须设置,也应该做好命名和管理工作。

docker 搭建lnmp开发环境

docker学习心得

前言

耗时一周零三天,终于用docker搭建起自己的开发环境。
详细过程:请参考https://segmentfault.com/a/1190000011912956

未分类

下面说说我的心路历程:(从一个系统说起deepin)
Ubuntu16.04用的好好地,突然看见17.10发布了,界面还挺好看。果断升级为17.10。然后发现,界面看着漂亮,用的时候很难受,快捷键和16.04也不太一样,装的软件各种打不开。经过深思熟虑我就换成deepin,各种常见的软件都能装,比如QQ,微信,搜狗等等,美滋滋。来搭建一下环境lnmp,然后,桌面没了。查了一下资料,说deepin桌面版不能装。心想,弄个虚拟机吧,可以各种折腾,折腾坏了重建就行了。想到最近docker很火,果断入坑。

作者经历的各种阶段:(各位可以参考,避免浪费时间)

第一阶段:找教程
极客学院这个不错,各种概念讲的很清楚,建议新学者直接通读一遍再动手。还有,不要全看,如果只是想我一样想在本地搭建docker环境。我给你总结几点:

  1. 了解docker三个概念,镜像,容器,仓库
  2. 掌握这么几个命令和对应命令的常用参数:docker run/ps/rm/rmi/start/stop/exec (创建并运行容器/查看运行的容器/删除容器/删除镜像/启动容器/停止容器/容器外进入容器)
  3. 知道Dockerfile是什么?能做什么?我刚开始,想着只用centos官方镜像通过Dockerfile创建出自己的lnmp,想着在Dockerfile里面完成php,mysql,nginx的编译安装,后面发现还不是照着网络上的编译过程各种复制粘贴,还各种报错,自己不会解决,何必自己坑自己。
  4. 知道docker-compose是什么?能做什么?(自己看教程)

第二阶段:安装docker
安装我就不说了,着重说一下docker加速器阿里云加速器

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://4qqg0972.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

第三阶段:学习命令
第四阶段:找别人搭建的lnmp ( https://www.awaimai.com/2120.html )
第五阶段:看完之后满脑袋为什么?开始研究,最后质疑别人搭建的
第六阶段:还是用别人搭建好的(自己搭建不出来)
第七阶段:发现和自己的需求不一致,配置文件对不上
第八阶段:自己搭
第九阶段:觉得自己搭建的很好,在这写个教程,哈哈

总结:这些阶段是作者真实经历过的,可能还比这个多。作为过来人,我只想说,一定要只做一件事,不要过多的去研究,我们只是想搭建本地运行环境。

自己搭建docker lnmp 过程:

第一步:docker pull centos # 拉取官方centos镜像
第二步:docker run -it –name ‘lnmp-self’ centos /bin/bash # 创建并运行容器
第三步:使用lnmp一键安装包
第四步:docker commit -a ‘amor’ -m ‘lnmp’ b7515f3e6a82 lnmp:1.4 # 基于已有镜像的容器创建新镜像
第五步:创建Dockerfile进行微调,可能需要安装新软件等
第六步:通过docker-compose 进行管理,开放端口,挂载数据卷(挂载配置,项目目录)

这样做有以下几点好处:

  1. 避免编译安装,降低Dockerfile复杂度,避免学习使用大量的linux命令,避免使用自己不熟悉的镜像
  2. 易理解,易管理,易扩展(都是自己弄得嘛,哈哈)

下面附上自己的Dockerfile,docker-compose内容,请在第五步和第六步之后参考
Dockerfile

FROM lnmp:1.4
MAINTAINER amor ([email protected])
# ssh
RUN yum install openssh-server -y
RUN mkdir -p /data/website/
CMD ["lnmp", "start"]

docker-compose

lnmp:
  build: .
  ports:
    - "80:80"
    - "443:443"
    - "22:22"
  volumes:
    # nginx 配置文件夹
    - ./conf/nginx/:/usr/local/nginx/conf/
    # mysql 配置文件
    - ./conf/mysql/my.cnf:/etc/my.cnf:rw
    # php配置文件
    - ./conf/php/:/usr/local/php/etc/
    # 项目目录
    - /data/Nutcloud/Ubuntu/website/:/data/website/
  tty: true

目录结构(仅供参考)

.
├── conf
│   ├── mysql
│   ├── nginx
│   └── php
├── docker-compose.yml
└── Dockerfile

配置一个安全的docker宿主机

如使用Linux默认安装来运行Docker守护进程和容器,会使你的主机出现安全和性能问题。 在本文中,我们将使用CentOS7的最小安装作为示例来说明如何配置来一个安全的docker运行宿主机。

前言

 本文以CentOS为例,介绍如何配置一个符合安全要求的docker运行宿主机。如使用默认安装来运行Docker守护进程和容器,会使你的主机出现安全和性能问题。 在本文中,我们将使用CentOS7的最小安装作为示例来说明如何配置来一个安全的docker运行宿主机。 CentOS已经大规模的应用到生产系统中,已被证明是一个稳定而安全的Linux版本。如果选择使用不同的Linux发行版,本文中的做法仍然值得参考,只需要将相关的示例命令转换为你的目标环境的命令。配置需要重点关注三个方面:

1、一个不需要任何额外服务和软件的操作系统,只运行Docker所需的工具。

2、安装并配置Docker守护程序以运行容器。 这包括性能和安全的设置,配置Docker更适合于生产环境而不是开发测试环境。

3、设置访问控制的安全策略,将防火墙配置为仅允许SSH和用于外部通信的容器必要端口。

安装操作系统

最小安装

     从官方镜像中最小安装CentOS 7, 如果以前安装过CentOS,还有一些步骤需要考虑:
  • 删除所有开发工具(编译器等)

  • 删除所有监听的服务,只保留22端口以进行SSH访问。 防火墙配置阻止其他正在运行的服务和删除多余服务

更新系统源

更新系统源确保所有库和程序都运行最新的版本。

sudo yum update –y

未分类

创建一个新用户

不能使用root用户运行容器,所以必须添加一个新的用户。

例如:添加用户名为dockeruser的新用户

1、向主机添加新用户。

adduser dockeruser

2、为新用户设置密码

passwd dockeruser

3、将用户添加到wheel组以提供sudo访问权限

usermod -a -G wheel dockeruser

未分类

生成SSHD密钥

     安全最佳做法是禁用使用密码的身份验证。我们可使密钥,并将其复制到服务器作为认证密钥。 以下步骤将是如何为linux或OSX主机生成密钥。

1、 在的客户端上生成ssh密钥

ssh-keygen –t rsa

2、运行此命令将在你的主目录的.ssh目录中创建两个文件

a. id_rsa –私钥

b. id_rsa.pub –公钥

3、使用新建用户dockeruser登录到Docker主机

mkdir ~/.ssh

sudo chmod 700 ~/.ssh

4 、 将公钥复制到Docker主机。 例如

scp ~/.ssh/id_rsa.pub [email protected]:~/.ssh/authorized_keys

5、现在可以不使用密码登录到主机

ssh [email protected]

SSH禁用root登录和使用密码的身份验证

禁用root用户登录到主机系统。 另外不允许使用密码登录。这是为了防止使用暴力破解主机系统的账号 ,在上面介绍中,向系统添加了一个新用户,并将其密钥复制到服务器。 使用该用户配置主机。

1、使用新添加的用户登录到主机

2、编辑SSH程序的配置文件禁用root登录

sudo vi /etc/ssh/sshd_config

3、找到以下文本:

#PermitRootLogin yes

4、更改内容为如下

PermitRootLogin no

5、找到以下文本的行

#PasswordAuthentication yes

6、更改内容为如下

PasswordAuthentication no

7、重启SSH服务

sudo systemctl restart sshd.service

停止SSHD以外的任何服务

出于安全考虑,宿主机是用于运行容器,因此,不应该运行任何多余的服务。建议更改ssh监听端口以进一步增强安全性。 对于本文中,已经更改SSH监听端口41022。

1、列出任何开放和侦听端口

sudo nmap -sU -sS -p 1-65535 localhost

未分类

在这种情况下,我们有两个打开和侦听的TCP端口。 41022端口是可以运行的ssh侦听端口 。25端口是SMTP服务,为系统默认安装,我们需要停止。

2、停止和删除postfix

a. 停止postfix服务

sudo systemctl stop postfix

b. 检查postfix是否还在监听端口25

sudo nmap -sU -sS -p 1-65535 localhost

未分类

c. 从主机中删除postfix

sudo yum remove postfix

安装和配置Docker

安装Docker

1、配置yum以查找Docker存储库

a. 创建一个新文件来保存Docker存储库的信息

sudo vi /etc/yum.repos.d/docker-ce.repo

b. 将以下内容添加到文件中:

[docker-ce-stable]

name=Docker CE Stable – $basearch
       baseurl=https://download.docker.com/linux/centos/7/$basearch/stable

enabled=1

gpgcheck=1

gpgkey=https://download.docker.com/linux/centos/gpg

c. 保存文件

2、安装Docker软件包

sudo yum install docker-ce –y

未分类

3、启动docker

sudo service docker start

Alternate: systemctl start docker

4、验证docker是否正常运行

sudo docker run hello-world

未分类

5 、配置Docker守护进程在开机时启动

sudo chkconfig docker on

Alternate: sudo systemctl enable docker

未分类

配置Docker

通过配置Docker来增加安全性,用于生产环境限制容器资源消耗。我们通过给docker守护进程传递命令行参数来执行操作。 我们将要使用的命令行总结如下

–icc=false

禁止容器之间通信。

  • 如果了解应用环境的网络拓扑,可以启用某几个容器与容器之间来进行通信使用—link参数

(–link=CONTAINER_NAME_or_ID:ALIAS)

  • 如果需要所有容器之间相互通信,可以将其设置为true,但这种情况在某种程度上是不安全的,它允许所有容器之间的完全网络通信。

–log-level“info”

将日志级别设置为info。 日志记录有多个级别,但有时记录内容非常多,会导致磁盘耗尽。日志级别的设置是为了获取需要的日志信息,而不是获取所有记录信息,而且有些日志信息无用且占用大量磁盘空间。

–iptables=true

启用iptables规则。

–default-ulimit

为容器设置默认的ulimits。这个参数将设置限制进程和文件的数量有关。这会确保容器不会占用过多的主机资源而造成主机宕机。

  • –default-ulimit nproc=1024:2048 –default-ulimit nofile=1020:2048

Docker进程启动参数非常多,本文中只说明配置方法,更多进程启动参数请参阅docker官网。下面是进程启动参数的配置方法:

1 、编辑docker.service文件

sudo vi /usr/lib/systemd/system/docker.service

2 、找到以下文本行

ExecStart=/usr/bin/dockerd

3 、修改为如下

ExecStart=/usr/bin/dockerd –icc=false –log-level  “info” –iptables=true –default-ulimit nproc=1024:2408–default-ulimit nofile=1024:2048 

4、重新加载

sudo systemctl daemon-reload

5、重新启动Docker服务

sudo service docker restart

6、验证命令行操作现在被传递给Docker

sudo ps -eaf | grep docker 

未分类

设置访问策略

在安全性较高的系统中,防火墙配置应启用白名单策略,默认禁用所有入站和出站流量。CentOS 7的防火墙默认使用firewalld,为了方便和docker容器访问控制集成,这里将禁用firewalld并使用iptables服务用于访问控制。

1 、禁用firewalld

sudo systemctl disable firewalld

2 、安装iptables-services

sudo yum install iptables-services –y

3 、启用iptables服务

sudo systemctl enable iptables

4、配置iptables规则(在最后的规则中,将替换为你的服务器ip-address)

sudo iptables -P INPUT DROP

sudo iptables -P FORWARD DROP

sudo iptables -A INPUT -p tcp -s 0/0 -d<serverip> –sport 513:65535 –dport 22 -mstate –state NEW,ESTABLISHED -j ACCEPT

5、保存iptables规则

sudo service iptables save

6 、验证iptables规则

     sudo iptables –L

通过安装nginx进行测试

让我们通过下载并运行nginx来测试Docker的安装。

1、下载images

sudo docker pull nginx (alpine /nginx:latest)

2 、启动nginx容器

sudo docker run –name docker-nginx -p 80:80 nginx

注意:-p 80:80将容器的端口80映射到主机外部端口80。

这条命令会修改iptables的规则,以允许流量通过端口80

3 、连接到端口80上主机的IP地址

http://serverip:80

你应该在浏览器中看到以下内容:

未分类

总结

本文介绍了如何通过安全配置来生成一个相对安全的docker运行宿主机,除此之外,还需通过安装最新的安全补丁并保持系统更新,定期安全巡检等来保证docker运行环境的安全性。

Ubuntu 14.04用apt在线/离线安装CDH5.1.2[Apache Hadoop 2.3.0]

1、CDH介绍

1.1、什么是CDH和CM?

  CDH一个对Apache Hadoop的集成环境的封装,可以使用Cloudera Manager进行自动化安装。
  Cloudera-Managerceql(本文中简称CM)是一个工具,CM能够管理一个大的Hadoop cluster并不是一只要下载tar files什么压缩并启动services这么简单。后续有非常多设定、监控等麻烦的事要处理,CM都能够做到,有些类似Casti。Cloudera Manager整合了一列的功能让系统管理者能更方便的维护Hadoop。
  

1.2、CDH的主要功能?

  • 管理
  • 监控
  • 诊断
  • 集成

1.3、CDH版本衍化

  Hadoop是一个开源项目,所以很多公司在这个基础进行商业化,Cloudera对hadoop做了相应的改变。
  Cloudera公司的发行版,我们将该版本称为CDH(Cloudera Distribution Hadoop)。截至目前为止,CDH共有5个版本,其中,前两个已经不再更新,最近的两个,分别是CDH4在Apache Hadoop 2.0.0版本基础上演化而来的),CDH5,它们每隔一段时间便会更新一次。
  Cloudera以Patch Level划分小版本,比如Patch Level为923.142表示在原生态Apache Hadoop 0.20.2基础上添加了1065个Patch(这些Patch是各个公司或者个人贡献的,在Hadoop jira上均有记录),其中923个是最后一个Beta版本添加的Patch,而142个是稳定版发行后新添加的Patch。由此可见,Patch Level越高,功能越完备且解决的Bug越多。
  Cloudera版本层次更加清晰,且它提供了适用于各种操作系统的Hadoop安装包,可直接使用apt-get或者yum命令进行安装,更加省事。

1.4、CDH5.1.2支持的主要组件简要介绍

  • HTTPFS
      1:Httpfs是Cloudera公司提供的一个Hadoop Hdfs的一个Http接口,通过WebHDFS REST API 可以对hdfs进行读写等访问
      2:与WebHDFS的区别是不需要客户端可以访问Hadoop集群的每一个节点,通过Httpfs可以访问放置在防火墙后面的Hadoop集群
      3:Httpfs是一个Web应用,部署在内嵌的Tomcat中

  • HBASE
      Hbase是Bigtable的开源山寨版本。是建立的Hdfs之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。
      它介于Bosql和RDBMS之间,仅能通过主键(row key)和主键的Range来检索数据,仅支持单行事务(可通过Hive支持来实现多表Join等复杂操作)。主要用来存储非结构化和半结构化的松散数据。
      与Hadoop一样,Hbase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。

  • HDFS
      Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。HDFS放宽了一部分POSIX约束,来实现流式读取文件系统数据的目的。HDFS在最开始是作为Apache Nutch搜索引擎项目的基础架构而开发的。HDFS是Apache Hadoop Core项目的一部分。

  • HIVE
      Hive 是一个基于 Hadoop 的开源数据仓库工具,用于存储和处理海量结构化数据。它把海量数据存储于 Hadoop 文件系统,而不是数据库,但提供了一套类数据库的数据存储和处理机制,并采用 HQL (类 SQL )语言对这些数据进行自动化管理和处理。我们可以把 Hive 中海量结构化数据看成一个个的表,而实际上这些数据是分布式存储在 HDFS 中的。 Hive 经过对语句进行解析和转换,最终生成一系列基于 hadoop 的 Map/Reduce 任务,通过执行这些任务完成数据处理。

  • HUE
      Hue是CDH专门的一套WEB管理器,它包括3个部分Hue Ui,Hue Server,Hue db。Hue提供所有的CDH组件的Shell界面的接口。你可以在Hue编写MR,查看修改HDFS的文件,管理Hive的元数据,运行Sqoop,编写Oozie工作流等大量工作。

  • Impala
      Impala对你存储在Apache Hadoop在HDFS,HBase的数据提供直接查询互动的SQL。除了像Hive使用相同的统一存储平台,Impala也使用相同的元数据,SQL语法(Hive SQL),ODBC驱动程序和用户界面(Hue Beeswax)。Impala还提供了一个熟悉的面向批量或实时查询和统一平台。

  • MapReduce
      MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念”Map(映射)”和”Reduce(归约)”,和他们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。他极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。 当前的软件实现是指定一个Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享相同的键组。MapReduce更多运行于离线系统,而实时计算,可以使用Storm。关于Sotrm的使用和介绍,可以参考这篇文章《ubuntu12.04+storm0.9.2分布式集群的搭建》。

  • Oozie
      Oozie是Yahoo针对Apache Hadoop开发的一个开源工作流引擎。用于管理和协调运行在Hadoop平台上(包括:HDFS、Pig和MapReduce)的Jobs。Oozie是专为雅虎的全球大规模复杂工作流程和数据管道而设计。

  • Solr
      Solr是一个基于Lucene的Java搜索引擎服务器。Solr 提供了层面搜索、命中醒目显示并且支持多种输出格式(包括 XML/XSLT 和 JSON 格式)。它易于安装和配置,而且附带了一个基于 HTTP 的管理界面。Solr已经在众多大型的网站中使用,较为成熟和稳定。Solr 包装并扩展了 Lucene,所以Solr的基本上沿用了Lucene的相关术语。更重要的是,Solr 创建的索引与 Lucene 搜索引擎库完全兼容。通过对 Solr 进行适当的配置,某些情况下可能需要进行编码,Solr 可以阅读和使用构建到其他 Lucene 应用程序中的索引。此外,很多 Lucene 工具(如Nutch、 Luke)也可以使用 Solr 创建的索引。

  • Spark
      Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用的并行计算框架,Spark基于map reduce算法实现的分布式计算,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的map reduce的算法。
      Spark和Storm类似,都是基于内存的运行,不确定哪种方式在数据吞吐量上要具优势,不过Storm计算时间延迟要小。关于Sotrm的使用和介绍,可以参考这篇文章《ubuntu12.04+storm0.9.2分布式集群的搭建》。

  • Sqoop
      Sqoop中一大亮点就是可以通过hadoop的mapreduce把数据从关系型数据库中导入数据到HDFS。sqoop架构非常简单,其整合了Hive、Hbase和Oozie,通过map-reduce任务来传输数据,从而提供并发特性和容错。sqoop主要通过JDBC和关系数据库进行交互。理论上支持JDBC的database都可以使用sqoop和hdfs进行数据交互。
      

  • YARN
      YARN可以理解为是Hadoop MapReduceV2版本,YARN重构根本的思想是将 JobTracker 两个主要的功能分离成单独的组件,这两个功能是资源管理和任务调度 / 监控。新的资源管理器全局管理所有应用程序计算资源的分配,每一个应用的 ApplicationMaster 负责相应的调度和协调。一个应用程序无非是一个单独的传统的 MapReduce 任务或者是一个 DAG( 有向无环图 ) 任务。ResourceManager 和每一台机器的节点管理服务器能够管理用户在那台机器上的进程并能对计算进行组织。
      事实上,每一个应用的 ApplicationMaster 是一个详细的框架库,它结合从 ResourceManager 获得的资源和 NodeManager 协同工作来运行和监控任务。

  • Zookeeper
     Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
      Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

2、CDH的官网在哪里?

  http://www.cloudera.com/

3、CDH在哪里下载?

  由于CDH有多个版本,作者不建议单独下载安装,可以通过cloudera-manager-daemons、cloudera-manager-server、cloudera-manager-agent来安装,本文后面会有介绍。

4、如何安装

4.1、设置Host

  修改Host

root@m1:~# cat /etc/hosts 
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.1.10    m1.linuxidc.com   m1
192.168.1.11    m2.linuxidc.com   m2
192.168.1.12    s1.linuxidc.com   s1
192.168.1.13    s2.linuxidc.com   s2


# The following lines are desirable for IPv6 capable hosts
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

4.2、设置静态IP

  修改成静态IP地址

root@m1:~# vi /etc/network/interfaces
iface wlan0 inet static
address 192.168.1.10
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 192.168.1.1
dns-nameservers 8.8.8.8

4.3、设置Host

  修改主机名称

root@m1:~# cat /etc/hostname 
m1.linuxidc.com

4.4、关闭防火墙

root@m1:~# ufw disable

4.5、安装JDK7,CDH5要求至少是Oracle JDK7

  添加 PPA repository 到系统

root@m1:~# add-apt-repository ppa:webupd8team/java
root@m1:~# apt-get update
root@m1:~# sudo apt-get upgrade
  • 1
  • 2
  • 3

  过程中会弹出个框,选择YES,因为要下载二进制包,所以可能会慢一些

root@m1:~# apt-get install oracle-java7-installer

  
  将Oracle 7 设置成默认版本

root@m1:~# apt-get install oracle-java7-set-default

   查看当前Java版本

root@m1:~# java -version 
java version "1.7.0_67"
Java(TM) SE Runtime Environment (build 1.7.0_67-b01) 
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)

4.6、安装MySql最新版,CM的数据库我们用My Sql管理

  安装Mysql Server,后面在安装Cloudera Manager的时候会用到,如果你要使用PostGreSQL ,可以跳过这一步 (4.6-4.8)。
  

4.6.1、在主机上使用apt-get安装My Sql

  在主机上使用apt-get安装My Sql,安装过程中会有提示,一直接回车就可以 (4台机器都要执行)

root@m1:~# apt-get install mysql-server

4.6.2、修改MySql配置,方便CM使用

4.6.2.1、配置MySql的监听地址

  对MySQL的配置项进行修改,先备份,找到“bind-address = 127.0.0.1”这一行,然后注释掉,改成“bind-address = 0.0.0.0”

root@m1:~# cp /etc/mysql/my.cnf /etc/mysql/my.cnf.bak 
root@m1:~# vi /etc/mysql/my.cnf 
#bind-address = 127.0.0.1 
bind-address = 0.0.0.0

4.6.2.2、配置MySql的其他配置,为了CM使用

  MySql配置中,其中对于Cloudera的支持,可以参考官方描述

[client]
default-character-set=utf8
[mysqld]
transaction-isolation=READ-COMMITTED
# Disabling symbolic-links is recommended to prevent assorted security risks;
# to do so, uncomment this line:
# symbolic-links=0
character-set-server=utf8
key_buffer              = 16M
key_buffer_size         = 32M
max_allowed_packet      = 32M
thread_stack            = 256K
thread_cache_size       = 64
query_cache_limit       = 8M
query_cache_size        = 64M
query_cache_type        = 1

max_connections         = 550

# log-bin should be on a disk with enough free space
# NOTE: replace '/x/home/mysql/logs/binary' below with
#       an appropriate path for your system.
log-bin=/x/home/mysql/logs/binary/mysql_binary_log

# For MySQL version 5.1.8 or later. Comment out binlog_format for older versions.
binlog_format           = mixed

read_buffer_size = 2M
read_rnd_buffer_size = 16M
sort_buffer_size = 8M
join_buffer_size = 8M

# InnoDB settings
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit  = 2
innodb_log_buffer_size          = 64M
innodb_buffer_pool_size         = 4G
innodb_thread_concurrency       = 8
innodb_flush_method             = O_DIRECT
innodb_log_file_size = 512M

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

4.7、创建后面在CM中会使用的数据库

root@m1:~# mysql -u root -p 
Enter password: 
Welcome to the MySQL monitor. Commands end with ; or g. 
Your MySQL connection id is 42 
Server version: 5.5.38-0ubuntu0.14.04.1 (Ubuntu) 

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. 

Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners. 

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. 

## Cloudera manager db user, database and grant
mysql> create user 'cmf'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

mysql> create database cmf  DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.00 sec) 

mysql> grant all privileges on cmf.* to 'cmf'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

## For activity monitor 
mysql> create user 'amon'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

mysql> create database amon  DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.00 sec) 

mysql> grant all privileges on amon.* to 'amon'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

## Hive Meta store 
mysql> create user 'hive'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

mysql> create database metastore  DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.00 sec) 

mysql> grant all privileges on metastore.* to 'hive'@'%' identified by 'xyz'; 
Query OK, 0 rows affected (0.00 sec) 

## Flush all changes 
mysql> FLUSH PRIVILEGES; 
Query OK, 0 rows affected (0.00 sec)

4.8、重启MySql,查看3306端口,并安装MySql、Java的支持

root@m1:~# service mysql restart
root@m1:~# netstat -tulpn | grep :3306 
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 2207/mysqld
root@m1:~# apt-get install libmysql-java 
Reading package lists... Done 
Building dependency tree 
Reading state information... Done 
Suggested packages: 
liblog4j1.2-java libcommons-logging-java 
The following NEW packages will be installed: 
libmysql-java 
0 upgraded, 1 newly installed, 0 to remove and 4 not upgraded. 
Need to get 894 kB of archives. 
After this operation, 1,060 kB of additional disk space will be used. 
Get:1 http://mirrors.sohu.com/ubuntu/ trusty/universe libmysql-java all 5.1.28-1 [894 kB] 
Fetched 894 kB in 1s (718 kB/s) 
Selecting previously unselected package libmysql-java. 
(Reading database ... 96338 files and directories currently installed.) 
Preparing to unpack .../libmysql-java_5.1.28-1_all.deb ... 
Unpacking libmysql-java (5.1.28-1) ... 
Setting up libmysql-java (5.1.28-1) ... 
root@m1:~#

4.9、安装Cloudera Manager组件

4.9.1、将Ubuntu 14.04暂时伪造成Ubuntu12.04

  Ubuntu 14.04暂时伪造成Ubuntu12.04,因为在写本文时CM现在还不支持14.04这个版本,CDH以后的版本应该会支持吧(4台机器都要执行)
  备份文件:

root@m1:~# cp /etc/lsb-release /etc/lsb-release.bak 

  使用VI编译文件,暂时修改成Ubuntu12.04 LTS的信息:

root@m1:~# vi /etc/lsb-release 
DISTRIB_ID=Ubuntu 
DISTRIB_RELEASE=12.04 
DISTRIB_CODENAME=precise 
DISTRIB_DESCRIPTION="Ubuntu 12.04.3 LTS"

4.9.2、升级Ubuntu的utils包,CM所必需要

  在所有机器上安装debian的包,升级utils,执行以下命令:(4台机器都要执行)

root@m1:~# wget http://ftp.cn.debian.com/debian/pool/main/f/fuse/fuse-utils_2.9.0-2+deb7u1_all.deb
root@m1:~# dpkg -i fuse-utils_2.9.0-2+deb7u1_all.deb

4.9.3、将CDH5的软件源添加到Apt中

root@m1:~# curl "http://archive.cloudera.com/cm5/ubuntu/precise/amd64/cm/cloudera.list" -o /etc/apt/sources.list.d/cloudera_precise.list
root@m1:~# curl -s http://archive.cloudera.com/cdh5/ubuntu/precise/amd64/cdh/archive.key | sudo apt-key add -
root@m1:~# apt-get update

4.9.4、安装Cloudera Manager

  安装Cloudera Manager(4台机器都要执行)

root@m1:~# apt-get install cloudera-manager-daemons cloudera-manager-server

4.9.5、修改Cloudera-Manager-Server的配置文件db.properties,设置MySql连接

  修改Cloudera-Manager-Server的配置文件db.properties,设置刚才我们在(4.7)Mysql中创建的cmf数据库、用户名、密码(4台机器都要执行)

root@m1:~# vi /etc/cloudera-scm-server/db.properties 
com.cloudera.cmf.db.type=mysql
com.cloudera.cmf.db.host=m1.linuxidc.com
com.cloudera.cmf.db.name=cmf
com.cloudera.cmf.db.user=cmf
com.cloudera.cmf.db.password=xyz

4.9.6、安装ntp ,CM agents需要用到”ntpdc”

  安装ntp ,CM agents需要用到”ntpdc”(4台机器都要执行)

root@m1:~# apt-get install ntp 
Reading package lists... Done 
Building dependency tree 
Reading state information... Done 
The following extra packages will be installed: 
libopts25 
Suggested packages: 
ntp-doc 
The following NEW packages will be installed: 
libopts25 ntp 
0 upgraded, 2 newly installed, 0 to remove and 4 not upgraded. 
Need to get 666 kB of archives. 
After this operation, 1,668 kB of additional disk space will be used. 
Do you want to continue? [Y/n] y 
Get:1 http://mirrors.sohu.com/ubuntu/ trusty/main libopts25 amd64 1:5.18-2ubuntu2 [55.3 kB] 
Get:2 http://mirrors.sohu.com/ubuntu/ trusty/main ntp amd64 1:4.2.6.p5+dfsg-3ubuntu2 [611 kB] 
Fetched 666 kB in 1s (537 kB/s) 
Selecting previously unselected package libopts25:amd64. 
(Reading database ... 95843 files and directories currently installed.) 
Preparing to unpack .../libopts25_1%3a5.18-2ubuntu2_amd64.deb ... 
Unpacking libopts25:amd64 (1:5.18-2ubuntu2) ... 
Selecting previously unselected package ntp. 
Preparing to unpack .../ntp_1%3a4.2.6.p5+dfsg-3ubuntu2_amd64.deb ... 
Unpacking ntp (1:4.2.6.p5+dfsg-3ubuntu2) ... 
Processing triggers for man-db (2.6.7.1-1) ... 
Processing triggers for ureadahead (0.100.0-16) ... 
Setting up libopts25:amd64 (1:5.18-2ubuntu2) ... 
Setting up ntp (1:4.2.6.p5+dfsg-3ubuntu2) ... 
* Starting NTP server ntpd [ OK ] 
Processing triggers for libc-bin (2.19-0ubuntu6.1) ... 
Processing triggers for ureadahead (0.100.0-16) ... 
root@m1:~#

4.9.7、重启Cloudera Manager Server

  启动Cloudera manager (4台机器都要执行)

root@m1:~# service cloudera-scm-server start 
Starting cloudera-scm-server: * cloudera-scm-server started 
root@m1:~#
  • 1
  • 2
  • 3

  查看Cloudera manager的启动日志,如果能够看到下面的类似信息,说明启动正确,tailf -100 /var/log/cloudera-scm-server/cloudera-scm-server.log或者/var/log/cloudera-scm-server/cloudera-scm-server.out,如果看不到类似下面的信息,那么也可以看到哪里出了问题,大多数都是和数据库的配置有关系。

2014-08-26 23:07:52,643 INFO [JvmPauseMonitor:debug.JvmPauseMonitor@236] Detected pause in JVM or host machine (e.g. a stop the world GC, or JVM not scheduled): paused approximately 1182ms: GC pool 'Copy' had collection(s): count=1 time=1641ms 
2014-08-26 23:07:52,644 INFO [JvmPauseMonitor:debug.JvmPauseMonitor@236] Detected pause in JVM or host machine (e.g. a stop the world GC, or JVM not scheduled): paused approximately 1634ms: GC pool 'Copy' had collection(s): count=1 time=1641ms 
2014-08-26 23:07:52,696 INFO [WebServerImpl:servlet.DispatcherServlet@339] FrameworkServlet 'Spring MVC Dispatcher Servlet': initialization completed in 18068 ms 
2014-08-26 23:07:52,793 INFO [WebServerImpl:cmon.JobDetailGatekeeper@127] ActivityMonitor configured to allow job details for all jobs. 
2014-08-26 23:07:53,407 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@399] Initializing SearchTemplateManager:2014-08-27T06:07:53.407Z 
2014-08-26 23:07:53,730 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@403] Generating entities:2014-08-27T06:07:53.730Z 
2014-08-26 23:07:53,821 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@407] Num entities:112 
2014-08-26 23:07:53,822 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@409] Generating documents:2014-08-27T06:07:53.822Z 
2014-08-26 23:07:53,891 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@411] Num docs:124 
2014-08-26 23:07:53,892 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@352] Constructing repo:2014-08-27T06:07:53.892Z 
2014-08-26 23:07:53,979 INFO [WebServerImpl:mortbay.log@67] jetty-6.1.26.cloudera.2 
2014-08-26 23:07:54,008 INFO [WebServerImpl:mortbay.log@67] Started [email protected]:7180 
2014-08-26 23:07:54,009 INFO [WebServerImpl:cmf.WebServerImpl@292] Started Jetty server. 
2014-08-26 23:07:55,276 INFO [SearchRepositoryManager-0:components.SearchRepositoryManager@354] Finished constructing repo:2014-08-27T06:07:55.276Z

4.10、通过Web方式继续安装

4.10.1、创建update-alternatives工具在/usr/sbin/下创建软链

  创建update-alternatives工具在/usr/sbin/下创建软链(在4台机器上一起执行)(4台机器都要执行)

root@m1:~# sudo ln -s /usr/bin/update-alternatives /usr/sbin/update-alternatives
  • 1

4.10.2、打开浏览器,输入 http://m1.linuxidc.com:7180/ 开始安装

  使用浏览器打开能够看到登录页面,那么恭喜你成功了。 http://m1.linuxidc.com:7180/(帐号和密码都是admin)

未分类

4.10.3、选择安装的CDH版本

  我们来安装Cloudera Express,之前官方有说这个版本有50个节点的限制,在CDH5.1.0版本也有看到,在写本文记录时的5.1.2版本不知道为什么我在安装的时候并未看到。
  
未分类

4.10.4、为CDH集群添加主机

  选择指定的CDH集群,可以添加多台机器,也可以使用IP匹配,输入完机器名称后,点search按钮,我输入的内容为”m1.linuxidc.com”
  
未分类

  CDH会根据你给出的内容,搜索到机器
  
未分类

4.10.5、选择CDH的安装方式

  选择存储方式,我们使用Parcel方式安装。系统会自动下载Parcel
  
未分类
  
  是否需要加密,如果你不考虑这些,可以不用选择这个复选框
  
未分类

  
  提供SSH登录凭据,这里建议使用root帐号,避免一些新同学,对其他帐号的权限设置不完整,导致后面安装出现错误。如何设置4台机器之前SSH免密码登录,就不在这里介绍了,如果不会,可以去搜索下。在这之前4台机器请配置好SSH免密码登录。
 
未分类
  
  安装过程中的状态,会下载CDH使用的包,可能会慢一些,要有耐心~~~~
  
未分类
  
  如果在安装过程中出现了以下错误“ImportError: No module named _io”,或者如下图中的提示,不用担心,这是一个已知问题。这是因为CDH5使用的Python版本问题。执行完下面的脚本后,点击重试就可以顺利的完成安装了。如果出现打不开CM Agent的log日志提示,那很可能是你的Host配置有问题,请参考本文最初写的Host配置。

root@m1:~# mv /usr/lib/cmf/agent/build/env/bin/python /usr/lib/cmf/agent/build/env/bin/python.bak
root@m1:~# cp /usr/bin/python2.7 /usr/lib/cmf/agent/build/env/bin/python

  安装完成了

未分类 

4.10.6、离线方式安装CDH的Parcel

  继续安装选定 Parcel

  如果你想要提高速度,可以按以下方法离线安装

    (1)、使用迅雷到http://archive.cloudera.com/cdh5/parcels/5.1.2/目录,下载CDH-5.1.2-1.cdh5.1.2.p0.3-precise.parcel和mainfest.json文件

    (2)、上传到/opt/cloudera/parcel-repo目录中,同时创建一个CDH-5.1.2-1.cdh5.1.2.p0.3-precise.parcel.sha文件,内容为”a492e4b6dece2850f0a37f2bf613ecb2980dfd37”,这个值可以在下载目录中的manifest.json文件中找到。然后看相对应的json->parcelName同一级的hash值。

    (3)、下载http://archive.cloudera.com/accumulo-c5/parcels/1.6.0.51/ACCUMULO-1.6.0-1.cdh5.1.0.p0.51-precise.parcel、http://archive.cloudera.com/sqoop-connectors/parcels/1.2/SQOOP_NETEZZA_CONNECTOR-1.2c5-precise.parcel、http://archive.cloudera.com/sqoop-connectors/parcels/1.2/SQOOP_TERADATA_CONNECTOR-1.2c5-precise.parcel

    (4)、参考步骤2,创建相应的.sha文件,.sha文件中的内容如下:

root@m1:/opt/cloudera/parcel-repo# echo "a719f373833a63108c616afb034d97c4e11405d5" >> ACCUMULO-1.6.0-1.cdh5.1.0.p0.51-precise.parcel.sha
root@m1:/opt/cloudera/parcel-repo# echo "a492e4b6dece2850f0a37f2bf613ecb2980dfd37"  >> CDH-5.1.2-1.cdh5.1.2.p0.3-precise.parcel.sha
root@m1:/opt/cloudera/parcel-repo# echo "48bedfff38f742d32541854e24b3310992616027" >> SQOOP_NETEZZA_CONNECTOR-1.2c5-precise.parcel.sha
root@m1:/opt/cloudera/parcel-repo# echo "76566b4797bd061d01cf25b36b21b26927ada9a4" >> SQOOP_TERADATA_CONNECTOR-1.2c5-precise.parcel.sha

    (5)、设置文件的使用权限

root@m1:/opt/cloudera/parcel-repo# chmod 777 -R .
root@m1:/opt/cloudera/parcel-repo# chown cloudera-scm:cloudera-scm ./*

    (6)、查看下载后的文件列表

root@m1:/opt/cloudera/parcel-repo# ll 
total 1709280 
drwxrwxrwx 2 cloudera-scm cloudera-scm 4096 Sep 1 01:16 ./ 
drwxr-xr-x 6 root root 4096 Sep 1 01:51 ../ 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 13204338 Aug 31 21:37 ACCUMULO-1.6.0-1.cdh5.1.0.p0.51-precise.parcel* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 41 Sep 1 01:15 ACCUMULO-1.6.0-1.cdh5.1.0.p0.51-precise.parcel.sha* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 1727519860 Sep 1 00:27 CDH-5.1.2-1.cdh5.1.2.p0.3-precise.parcel* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 41 Sep 1 01:15 CDH-5.1.2-1.cdh5.1.2.p0.3-precise.parcel.sha* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 41602 Sep 1 01:13 SQOOP_NETEZZA_CONNECTOR-1.2c5-precise.parcel* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 41 Sep 1 01:16 SQOOP_NETEZZA_CONNECTOR-1.2c5-precise.parcel.sha* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 9499051 Sep 1 01:13 SQOOP_TERADATA_CONNECTOR-1.2c5-precise.parcel* 
-rwxrwxrwx 1 cloudera-scm cloudera-scm 41 Sep 1 01:16 SQOOP_TERADATA_CONNECTOR-1.2c5-precise.parcel.sha*

未分类
   
  主机正确性检查,和当前m1.linuxidc.com的CDH5.12组件版本汇总。
  
未分类

未分类    

4.10.7、在m1.linuxidc.com上安装ZooKeeper服务

  选择要安装的集群服务,我们来安装ZooKeeper。如下图选择

未分类
  
  自定义角色分配

未分类
  
  数据库设置,我们输入之前在MySql中(4.7)创建的Activity Monitor使用的数据库amon以及用户名、密码,点击测试链接,可以看到成功信息。
  
未分类
  
  审核更改,如果你没有邮件要配置,可以什么都不用输入

未分类
  
  
  升级完成,并且成功启动ZooKeeper、CM Service服务。

未分类  

4.10.8、初始化完成,进入CM主页

  升级完成,就可以进入主页

未分类
  
  查看m1.linuxidc.com的运行状态

未分类

4.10.9、恢复Ubuntu 14.04版本信息

  最后我们恢复ubuntu 14.04版本信息

root@cm1:~# rm /etc/lsb-release /etc/lsb-release.bak
root@cm1:~# mv /etc/lsb-release.bak  /etc/lsb-release
  • 1
  • 2

4.11、通过CM管理多个集群

4.11.1、添加1个新的集群,机器名称为m2.linuxidc.com

  有的时候,因为业务原因,不同的服务我们要使用不同的集群,便于管理和维护,下面我们再使用CM来测试创建一个新集群Cluster 2
  添加其他主机,我们测试将m2.linuxidc.com也做为一个新集群添加到CM中。点击CM主页右上角的添加集群,会重复看到(参考4.10.4章节)的页面
  
未分类

未分类

  再继续安装,可以参考(4.10.5章节-4.10.7章节)的页面重复操作,下面只给出将m2.linuxidc.com加入到集群的安装过程中部分截图。
  m2.linuxidc.com机器上要有cloudera-manager-server、cloudera-manager-daemons、cloudera-manager-agent(这个可以不用安装,但机器上要有包)。cloudera-manager-server、cloudera-manager-daemons的安装,可以参考4.9.4章节,配置可以参考4.9.5章节,服务是启动状态。
  
未分类

未分类

未分类

4.11.2、完成安装,验证m2.linuxidc.com是否成功地添加到集群Cluster 2

未分类

4.12、向1个集群添加1台新主机

4.12.1、开始添加,输入要添加的机器名称s1.linuxidc.com

未分类
  
  选择存储库,为了保持统一,建议使用与当前CM一样的版本,然后会继续走4.10.5的步骤
  
未分类
  
  再继续安装,可以参考(4.10.5章节-4.10.7章节)的页面重复操作,下面只给出将s1.linuxidc.com加入到集群的安装过程中部分截图。
  m2.linuxidc.com机器上要有cloudera-manager-server、cloudera-manager-daemons、cloudera-manager-agent(这个可以不用安装,但机器上要有包)。
  cloudera-manager-server、cloudera-manager-daemons的安装,可以参考4.9.4章节,配置参考4.9.5章节。cloudera-manager-server的服务要在启动状态。

未分类

未分类

4.12.2、选择主机模

未分类

未分类

未分类

4.12.3、完成将s1.linuxidc.com添加到集群

未分类

4.12.4、验证s1.linuxidc.com是否成功地添加到集群Cluster 1

未分类

未分类

  到此为主,在Ubuntu 14.04下使用apt-get方式,安装CDH5.12已经成功。并且支持中文,后面有什么服务或者机器要添加的,可以自己来做了。

5、FAQ

5.1、过程中如果出现”Incorrect string value: ‘x“的提示

  是和数据库的编码有关,在mysql中执行以下语句:

alter table CLIENT_CONFIGS convert to character set utf8;
alter table CLUSTERS convert to character set utf8;
alter table CLUSTERS_AUD convert to character set utf8;
alter table CLUSTER_ACTIVATED_RELEASES convert to character set utf8;
alter table CLUSTER_ACTIVATED_RELEASES_AUD convert to character set utf8;
alter table CLUSTER_MANAGED_RELEASES convert to character set utf8;
alter table CLUSTER_UNDISTRIBUTED_RELEASES convert to character set utf8;
alter table CM_PEERS convert to character set utf8;
alter table CM_VERSION convert to character set utf8;
alter table COMMANDS convert to character set utf8;
alter table COMMAND_SCHEDULES convert to character set utf8;
alter table CONFIGS convert to character set utf8;
alter table CONFIGS_AUD convert to character set utf8;
alter table CONFIG_CONTAINERS convert to character set utf8;
alter table CREDENTIALS convert to character set utf8;
alter table GLOBAL_SETTINGS convert to character set utf8;
alter table HOSTS convert to character set utf8;
alter table HOSTS_AUD convert to character set utf8;
alter table HOST_TEMPLATES convert to character set utf8;
alter table HOST_TEMPLATE_TO_ROLE_CONF_GRP convert to character set utf8;
alter table METRICS convert to character set utf8;
alter table PARCELS convert to character set utf8;
alter table PARCEL_COMPONENTS convert to character set utf8;
alter table PROCESSES convert to character set utf8;
alter table PROCESS_ACTIVE_RELEASES convert to character set utf8;
alter table RELEASES convert to character set utf8;
alter table RELEASES_AUD convert to character set utf8;
alter table REVISIONS convert to character set utf8;
alter table ROLES convert to character set utf8;
alter table ROLES_AUD convert to character set utf8;
alter table ROLE_CONFIG_GROUPS convert to character set utf8;
alter table ROLE_CONFIG_GROUPS_AUD convert to character set utf8;
alter table ROLE_STALENESS_STATUS convert to character set utf8;
alter table SCHEMA_VERSION convert to character set utf8;
alter table SERVICES convert to character set utf8;
alter table SERVICES_AUD convert to character set utf8;
alter table SNAPSHOT_POLICIES convert to character set utf8;
alter table USERS convert to character set utf8;
alter table USER_ROLES convert to character set utf8;
alter table USER_SETTINGS convert to character set utf8;