Debian配置iptables

Debian默认已经安装iptables,查看规则iptables -L默认允许所有出入,这是非常不安全的,因此需要对规则进行调整。

编辑配置文件:

/etc/iptables.test.rules

添加下面的规则,请根据实际情况调整:

*filter

# Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

# Accepts all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allows all outbound traffic
# You could modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

# Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# Allows SSH connections 
# The --dport number is the same as in /etc/ssh/sshd_config
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Now you should read up on iptables rules and consider whether ssh access 
# for everyone is really desired. Most likely you will only allow access from certain IPs.

# Allow ping
#  note that blocking other types of icmp packets is considered a bad idea by some
#  remove -m icmp --icmp-type 8 from this line to allow all kinds of icmp:
#  https://security.stackexchange.com/questions/22711
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# log iptables denied calls (access via 'dmesg' command)
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Reject all other inbound - default deny unless explicitly allowed policy:
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

激活规则:

iptables-restore < /etc/iptables.test.rules

保存规则到主配置文件:

iptables-save > /etc/iptables.up.rules

开机自动加载规则

#编辑配置
/etc/network/if-pre-up.d/iptables

添加如下内容

#!/bin/sh
 /sbin/iptables-restore < /etc/iptables.up.rules

添加执行权限

chmod +x /etc/network/if-pre-up.d/iptables

OK,一切皆已搞定,感觉比CentOS的iptables要麻烦一点。

如何让 Docker 容器正常打印 Python 的日志

在 Docker 容器里跑 Python 程序时,我们经常遇到通过print函数或者logging模块输出的信息在容器 log 中迷之失踪,过了好久又迷之出现。这是因为 Python 在写 stdout 和 stderr 的时候有缓冲区,导致输出无法实时更新进容器 log。

有如下几种方法解决:

1. 增加环境变量

对于使用print函数打印的内容,在运行容器时增加环境变量PYTHONUNBUFFERED=0就可以解决。

2. 配置 logging 的 stream 参数

import logging
logging.basicConfig(stream=sys.stdout)

这样,通过 logging 模块打印的日志都会直接写到标准输出 stdout。

或者自定义两个StreamHandler分别配置为输出到 stdout 和 stderr,来对不同 log 分别进行输出处理。

3. WSGI server 配置参数

如果是以 WSGI server 运行的 web 应用,以 gunicorn 为例,在 gunicorn 的启动命令中增加参数–access-logfile – –error-logfile -即可。

docker~使用阿里加速器

国外的docker hub速度慢这是公认的,而我们可以使用阿里提供的加速器,管理你的镜像,拉别人的镜像等等.

1、注册一个阿里的账号

2、进行加速器页面https://cr.console.aliyun.com/#/accelerator

3、复制你的加入器URL

4、将加速参数添加到docker启动项

echo “DOCKER_OPTS=”$DOCKER_OPTS –registry-mirror=你的加速地址”” | sudo tee -a /etc/default/docker
sudo service docker restart

5、也可以将加速器地址写到配置文件里,然后重启服务即可

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'

{

  "registry-mirrors": ["https://d8b3zdiw.mirror.aliyuncs.com"]

}

EOF

sudo systemctl daemon-reload

sudo systemctl restart docker[/code]

6、如果客户端docker版本低,也可以去升级一下

curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh -[/code]

7、可以在阿里网页上搜索你需要的镜像,https://cr.console.aliyun.com/#/imageSearch

未分类

8、点击详情,可以看到镜像相关信息,和如何拉取你的镜像

未分类

9、建立自己的仓库之后,可以push和pull自己的镜像了

未分类

感谢各位的阅读,我们对docker的研究还在继续!

centos7下docker二进制文件编译

系统环境与软件版本

  • OS:Centos7 64bit

  • Kernel Version:3.10.0-693.2.2.el7.x86_64

  • Golang Version: go1.8.4 linux/amd64

  • Docker: 17.05.0-ce

浅谈docker源码编译

官方提供编译步骤依次为:make build和make binary。先看懂Makefile会帮助理解docker基本结构。

  • make build
    其实就是docker build,于是要看Dockerfile文件。其制作一个叫docker-dev的镜像,镜像中会生成源码编译的环境。

  • make binary
    其实就是docker run docker-dev,即运行docker-dev一个容器,并在容器中的bundles文件夹下生成dockers所需的二进制文件。

通过查看Dockerfile以下内容:

# Install tomlv, vndr, runc, containerd, tini, docker-proxy
# Please edit hack/dockerfile/install-binaries.sh to update them.
COPY hack/dockerfile/binaries-commits /tmp/binaries-commits
COPY hack/dockerfile/install-binaries.sh /tmp/install-binaries.sh
RUN /tmp/install-binaries.sh tomlv vndr runc containerd tini proxy bindata

可以看出具体的binary来自脚本install-binaries.sh。

  • install-binaries.sh
    此脚本涉及到docker-containerd系列,docker-runc,docker-init和docker-proxy等组件的源码地址,以及编译命令

  • binaries-commits
    此文件涉及到各类组件的commit编号,使用git checkout -q xxxxxxxx来切换到相对应的tree上

以下内容,实际便是抽出install-binaries.sh中的内容,独立完成,从而获得docker所有编译后的二进制文件。如要生成rpm文件,需进一步研究

Golang的安装与配置

可以在 下载 – Golang中国 (https://www.golangtc.com/download) 中下载相对应的安装包。安装包go1.9.linux-amd64.tar.gz和脚本install_go.sh放在同一个目录下。

代码下载: https://o-my-chenjian.com/download/Make-Docker-Executable-File-On-CentOS7/install_go.sh

#!/bin/bash

cur_path=`pwd`

# 解压go包
sudo tar zxvf ${cur_path}/go1.8.4.linux-amd64.tar.gz -C /usr/local

# 创建GOPATH文件夹
sudo mkdir -p /home/mygo

# 设置go环境变量
sudo echo "export GOROOT=/usr/local/go" >> /etc/profile
sudo echo "export GOPATH=/home/mygo" >> /etc/profile
sudo echo "export PATH=$PATH:$GOROOT/bin" >> /etc/profile
. /etc/profile

# 安装wget
sudo yum install -y wget

# 更新为aliyun源
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

# 安装git
sudo yum install -y git

# 安装go包管理工具govendor
go get -u github.com/kardianos/govendor
cp ${GOPATH}/bin/govendor /usr/local/go/bin/

结果如下:

go version
<<'COMMENT'
go version go1.8.4 linux/amd64
COMMENT

ls /home/mygo/ /home/mygo/src/ /home/mygo/src/github.com/ /usr/local/go/bin/

<<'COMMENT'
/home/mygo/:
bin  pkg  src

/home/mygo/src/:
github.com

/home/mygo/src/github.com/:
kardianos

/usr/local/go/bin/:
go  godoc  gofmt  govendor
COMMENT

docker编译二进制文件

安装依赖软件

yum install -y gcc make cmake device-mapper-devel btrfs-progs-devel libarchive libseccomp-devel glibc-static

编译docker等二进制文件

组件:

  • docker: docker-client端
  • dockerd: docker-server端

操作:

cd $GOPATH/src/github.com/

mkdir docker && cd docker

# 下载相关版本的docker源码
git clone -b v17.05.0-ce https://github.com/moby/moby.git

cp -R moby/ docker && rm -rf moby/

# docker编译
cd  $GOPATH/src/github.com/docker/docker/cmd/docker
go build
cp docker /usr/local/bin/

# dockerd编译
cd  $GOPATH/src/github.com/docker/docker/cmd/dockerd
go build
cp dockerd /usr/local/bin/

编译containerd等二进制文件

组件:

  • docker-containerd
  • docker-containerd-ctr
  • docker-containerd-shim

操作:

# 下载相关版本的container源码
git clone https://github.com/containerd/containerd.git "${GOPATH}/src/github.com/docker/containerd"
cd "${GOPATH}/src/github.com/docker/containerd"
git checkout -q 9048e5e50717ea4497b757314bad98ea3763c145

# 组件编译
cd  ${GOPATH}/src/github.com/docker/containerd

make static
<<'COMMENT'
cd ctr && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/ctr
cd containerd && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/containerd
cd containerd-shim && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/containerd-shim
COMMENT

cp bin/containerd /usr/local/bin/docker-containerd
cp bin/containerd-shim /usr/local/bin/docker-containerd-shim
cp bin/ctr /usr/local/bin/docker-containerd-ctr

编译docker-runc二进制文件

组件:

  • docker-runc

操作:

cd $GOPATH/src/github.com/

mkdir opencontainers && cd opencontainers

# 下载相关版本的runc源码
git clone -b v1.0.0-rc2 https://github.com/opencontainers/runc.git "${GOPATH}/src/github.com/opencontainers/runc"

# runc编译
cd  ${GOPATH}/src/github.com/opencontainers/runc

make BUILDTAGS="${RUNC_BUILDTAGS:-"selinux"}" static

<<'COMMENT'
CGO_ENABLED=1 go build -i -tags "selinux cgo static_build" -ldflags "-w -extldflags -static -X main.gitCommit="c91b5bea4830a57eac7882d7455d59518cdf70ec-dirty" -X main.version=1.0.0-rc2" -o runc .
COMMENT

cp runc /usr/local/bin/docker-runc

编译docker-init二进制文件

组件:

docker-init

操作:

cd $GOPATH/src/github.com/

mkdir krallin && cd krallin

# 下载相关版本的tini源码
git clone https://github.com/krallin/tini.git "$GOPATH/tini"
cd "$GOPATH/tini"
git checkout -q 949e6facb77383876aeff8a6944dde66b3089574

cmake .
<<'COMMENT'
-- The C compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Performing Test HAS_BUILTIN_FORTIFY
-- Performing Test HAS_BUILTIN_FORTIFY - Failed
-- Configuring done
-- Generating done
-- Build files have been written to: /home/mygo/tini
COMMENT

make tini-static
<<'COMMENT'
Scanning dependencies of target tini-static
[100%] Building C object CMakeFiles/tini-static.dir/src/tini.c.o
Linking C executable tini-static
[100%] Built target tini-static
COMMENT

cp tini-static /usr/local/bin/docker-init

编译docker-proxy二进制文件

组件:

docker-proxy

操作:

cd $GOPATH/src/github.com/docker

# 下载相关版本的proxy源码
git clone https://github.com/docker/libnetwork.git "$GOPATH/src/github.com/docker/libnetwork"
cd "$GOPATH/src/github.com/docker/libnetwork"
git checkout -q 7b2b1feb1de4817d522cc372af149ff48d25028

# proxy编译
go build -ldflags="$PROXY_LDFLAGS" -o /usr/local/bin/docker-proxy github.com/docker/libnetwork/cmd/proxy

运行docker

ll /usr/local/bin/docker*

<<'COMMENT'
-rwxr-xr-x. 1 root root 25845680 Oct 10 14:00 /usr/local/bin/docker
-rwxr-xr-x. 1 root root 12474568 Oct 10 14:01 /usr/local/bin/docker-containerd
-rwxr-xr-x. 1 root root 11435336 Oct 10 14:03 /usr/local/bin/docker-containerd-ctr
-rwxr-xr-x. 1 root root  3858880 Oct 10 14:04 /usr/local/bin/docker-containerd-shim
-rwxr-xr-x. 1 root root 55072232 Oct 10 14:02 /usr/local/bin/dockerd
-rwxr-xr-x. 1 root root   824568 Oct 10 14:59 /usr/local/bin/docker-init
-rwxr-xr-x. 1 root root  2528043 Oct 10 15:10 /usr/local/bin/docker-proxy
-rwxr-xr-x. 1 root root 10894408 Oct 10 14:16 /usr/local/bin/docker-runc
COMMENT

groupadd docker

dockerd
<<'COMMENT'
INFO[0000] libcontainerd: new containerd process, pid: 9024 
WARN[0000] containerd: low RLIMIT_NOFILE changing to max  current=1024 max=4096
WARN[0001] failed to rename /var/lib/docker/tmp for background deletion: %!s(<nil>). Deleting synchronously 
INFO[0001] [graphdriver] using prior storage driver: overlay 
INFO[0001] Graph migration to content-addressability took 0.00 seconds 
INFO[0001] Loading containers: start.
INFO[0001] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address 
INFO[0001] Loading containers: done.
INFO[0001] Daemon has completed initialization
INFO[0001] Docker daemon                                 commit=library-import graphdriver=overlay version=library-import
INFO[0001] API listen on /var/run/docker.sock
COMMENT

ps aux|grep docker
<<'COMMENT'
root     11753  0.2  2.8 343472 28860 pts/1    Sl+  14:09   0:00 dockerd
root     11756  0.0  0.6 267908  6240 ?        Ssl  14:09   0:00 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root     11890  0.0  0.0 112660   976 pts/2    R+   14:10   0:00 grep --color=auto docker
COMMENT

docker version
<<'COMMENT'
Client:
 Version:      library-import
 API version:  1.29
 Go version:   go1.8.4
 Git commit:   library-import
 Built:        library-import
 OS/Arch:      linux/amd64

Server:
 Version:      library-import
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.8.4
 Git commit:   library-import
 Built:        library-import
 OS/Arch:      linux/amd64
 Experimental: false
COMMENT

docker pull busybox

docker run -idt busybox
<<'COMMENT'
b6f61b4b5ec9ce2b4331a47a1e8a3552b2162ca0f152ec2e02dfbf169d64a80a
COMMENT

docker ps -a
<<'COMMENT'
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
b6f61b4b5ec9        busybox             "sh"                27 minutes ago      Up 27 minutes                           loving_noether
COMMENT

可以看到,在启动dockerd后,会启动另一个程序docker-containerd。

制作docker的systemd-unit

docker.service

cat > docker.service <<EOF
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io

[Service]
Environment="PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin"
EnvironmentFile=-/run/flannel/docker
ExecStart=/usr/local/bin/dockerd --log-level=error $DOCKER_NETWORK_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

启动docker服务

sudo cp docker.service /etc/systemd/system/docker.service
sudo systemctl daemon-reload

sudo systemctl enable docker
<<'COMMENT'
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /etc/systemd/system/docker.service.
COMMENT

sudo systemctl start docker

sudo systemctl status docker
<<'COMMENT'
● docker.service - Docker Application Container Engine
   Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2017-10-11 15:27:51 CST; 3s ago
     Docs: http://docs.docker.io
 Main PID: 1152 (dockerd)
   Memory: 67.2M
   CGroup: /system.slice/docker.service
           ├─1152 /usr/local/bin/dockerd --log-level=error
           └─1156 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd...

Oct 11 15:27:51 localhost.localdomain systemd[1]: Started Docker Application Container Engine.
Oct 11 15:27:51 localhost.localdomain systemd[1]: Starting Docker Application Container Engine...
COMMENT

TroubleShooting

  • /bin/bash^M: bad interpreter:没有那个文件或目录

参考: http://blog.csdn.net/yongan1006/article/details/8142527

  • Linux环境下gcc静态编译/usr/bin/ld: cannot find -lc错误

参考: http://blog.csdn.net/shudaqi2010/article/details/32938715

docker国内镜像设置

linux系统

如果存在/etc/docker/目录,那么恭喜,很简单,在该目录下添加一个叫做daemon.json的文件。

内容如下:

{ 
“registry-mirrors”: [“https://registry.docker-cn.com“] 
}

然后重新启动docker daemon服务即可。

上述方法是参考:
https://docs.docker.com/registry/recipes/mirror/#use-case-the-china-registry-mirror

mac系统

mac系统有docker设置界面。在右上角docker小鲸鱼图标的Preference里面的Advanced里面的Registry mirrors里面添加https://registry.docker-cn.com即可。然后重新启动docker。

Docker下Mysql .cnf文件修改小贴士

一朝选Docker,就要会填坑,今天因为一不小心,改坏了测试环境的mysql配置,导致container启动不起来,然后想方设法恢复容器中文件,费了一中午时间终于在不做新镜像,不起新容器的情况下修复了错误配置。今天就来说说几种修改MySQL配置*.cnf文件修改方法。

环境

  • docker 17.06.0-ce
  • CentOS Linux 7 (Core)
  • MySQL 5.7.18 (docker镜像)

1. 映射目录(未雨绸缪)

在启动映射 /etc/mysql/目录到本地没有亲测 这个初学docker的基本都会。最简单的,修改本地文件重启container 即可。当你对一个镜像结构非常熟悉的时候可以用。 mysql官方镜像上也有说怎么映射这个目录出来,只是当时用的太匆忙这些没有做。

未分类

2. 容器bash或sh(紧急处理)

这种方法相对比较容易,用docker exec处理进入bash 或sh 操作即可。

docker exec -it <containerid> bash

安装vim

apt-get update
apt-get install vim

MySQL配置文件所在目录/etc/mysql/

vi /etc/mysql/my.cnf

未分类

my.cnf文件内容 在这个环境下,我把my.cnf文件给写错了导致 容器一直启动不起来,然后就无法exec bash了,我只能另球方法把 my.cnf修改回来。

(PS:结束exec bash ctrl+p,ctrl+q 组合命令)

3. docker cp 命令(安慰剂)

https://stackoverflow.com/questions/24553790/how-to-edit-docker-container-files-from-the-host 博主参考这个文章才知道的,虽然最佳答案不是关于cp命令的。官方docker container cp (https://docs.docker.com/engine/reference/commandline/container_cp/) 操作说明

$ docker cp CONTAINER:FILEPATH LOCALFILEPATH
$ vi LOCALFILEPATH
$ docker cp LOCALFILEPATH CONTAINER:FILEPATH

可以将本地的my.cnf 文件拷入到容器中,可以将容器中的文件拷出,解决了用

  • docker exec:只能对运行中的容器操作文件
  • docker run: 加-v映射文件目录出来 参数只能创建新容器

这两个痛点,重新将修改正确的cnf文件拷贝回container,于是乎坏掉container的又可以奇迹般的使用了。

4. 绕圈子解决办法(待测试)

参考 https://github.com/moby/moby/issues/18078

Commit the stopped container:
docker commit $STOPPED_CONTAINER user/test_image
Start/run with a different entry point:
docker run -ti --entrypoint=sh user/test_image

意思就是把坏掉的容器提交成镜像,然后再镜像启动的时候用ti模式加入entrypoint=sh 去修复问题。但是修复完怎么处理,还不知道。

5. 进阶docker container inspect

这个方法不是解决突然改坏文件的,通过这个方法可以访问到容器的配置文件 containerid(系统生成的).json文件。应该是可以修改json文件调整容器启动属性的。这个操作比较高深,也比较危险,目前我还用不到,如果有正好需要的可以研究下,这里只做一个引子。过程可以参考 https://stackoverflow.com/questions/32750748/how-to-edit-files-in-stopped-not-starting-docker-container

未分类

窥探一下 我也偷偷的看了一下,看花眼的配置,大家要玩这个配置的时候一定要小心哦~

Docker镜像的存储机制

近几年 Docker 风靡技术圈,不少从业人员都或多或少使用过,也了解如何通过 Dockerfile 构建镜像,从远程镜像仓库拉取自己所需镜像,推送构建好的镜像至远程仓库,根据镜像运行容器等。这个过程十分简单,只需执行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想过镜像在本地到底是如何存储的?容器又是如何根据镜像启动的?推送镜像至远程镜像仓库时,服务器又是如何存储的呢?下面我们就来简单聊一聊。

未分类

  
Docker 镜像本地存储机制及容器启动原理

  Docker 镜像不是一个单一的文件,而是有多层构成。我们可通过 docker images 获取本地的镜像列表及对应的元信息, 接着可通过docker history 查看某个镜像各层内容及对应大小,每层对应着 Dockerfile 中的一条指令。Docker 镜像默认存储在 /var/lib/docker/中,可通过 DOCKER_OPTS 或者 docker daemon 运行时指定 –graph= 或 -g 指定。

  Docker 使用存储驱动来管理镜像每层内容及可读写的容器层,存储驱动有 DeviceMapper、AUFS、Overlay、Overlay2、Btrfs、ZFS 等,不同的存储驱动实现方式有差异,镜像组织形式可能也稍有不同,但都采用栈式存储,并采用 Copy-on-Write(CoW) 策略。且存储驱动采用热插拔架构,可动态调整。那么,存储驱动那么多,该如何选择合适的呢?大致可从以下几方面考虑:

  若内核支持多种存储驱动,且没有显式配置,Docker 会根据它内部设置的优先级来选择。优先级为 AUFS > Btrfs/ZFS > Overlay2 > Overlay > DeviceMapper。若使用 DeviceMapper 的话,在生产环境,一定要选择 direct-lvm, loopback-lvm 性能非常差。

  选择会受限于 Docker 版本、操作系统、系统版本等。例如,AUFS 只能用于 Ubuntu 或 Debian 系统,Btrfs 只能用于 SLES (SUSE Linux Enterprise Server, 仅 Docker EE 支持)。

  有些存储驱动依赖于后端的文件系统。例如,Btrfs 只能运行于后端文件系统 Btrfs 上。

  不同的存储驱动在不同的应用场景下性能不同。例如,AUFS、Overlay、Overlay2 操作在文件级别,内存使用相对更高效,但大文件读写时,容器层会变得很大;DeviceMapper、Btrfs、ZFS 操作在块级别,适合工作在写负载高的场景;容器层数多,且写小文件频繁时,Overlay 效率比 Overlay2 更高;Btrfs、ZFS 更耗内存。

  Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里做的所有改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。若要持久化这些改动,须通过 docker commit [repository[:tag]] 将当前容器保存成为一个新镜像。若想将数据持久化,或是多个容器间共享数据,需将数据存储在 Docker volume 中,并将 volume 挂载到相应容器中。

  存储驱动决定了镜像及容器在文件系统中的存储方式及组织形式,下面分别对常见的 AUFS、Overlay 作一简单介绍。

  
AUFS

AUFS 简介

  AUFS 是 Debian (Stretch 之前的版本,Stretch默认采用 Overlay2) 或 Ubuntu 系统上 Docker 的默认存储驱动,也是 Docker 所有存储驱动中最为成熟的。具有启动快,内存、存储使用高效等特点。如果使用的 Linux 内核版本为 4.0 或更高,且使用的是 Docker CE,可考虑使用Overlay2 (比 AUFS 性能更佳)。

配置 AUFS 存储驱动

① 验证内核是否支持 AUFS

$ grep aufs /proc/filesystems nodev aufs

  
② 若内核支持,可在 docker 启动时通过指定参数 –storage-driver=aufs 选择 AUFS

AUFS 存储驱动工作原理

采用 AUFS 存储驱动时,有关镜像和容器的所有层信息都存储在 /var/lib/docker/aufs/ 目录下,下面有三个子目录:

  • /diff:每个目录中存储着每层镜像包含的真实内容

  • /layers:存储有关镜像层组织的元信息,文件内容存储着该镜像的组建镜像列表

  • /mnt:挂载点信息存储,当创建容器后,mnt 目录下会多出容器对应的层及该容器的 init 层。目录名称与容器 ID 不一致。实际的读写层存储在 /var/lib/docker/aufs/diff,直到容器删除,此读写层才会被清除掉。

采用 AUFS 后容器如何读写文件?

读文件

容器进行读文件操作有以下三种场景:

  • 容器层不存在: 要读取的文件在容器层中不存在,存储驱动会从镜像层逐层向下找,多个镜像层中若存在同名文件,上层的有效。

  • 文件只存在容器层:读取容器层文件

  • 容器层与镜像层同时存在:读取容器层文件

修改文件或目录

容器中进行文件的修改同样存在三种场景:

  • 第一次写文件:若待修改的文件在某个镜像层中,AUFS 会先执行 copy_up 操作将文件从只读的镜像层拷贝到可读写的容器层,然后进行修改。在文件非常大的情况下效率比较低下。

  • 删除文件:删除文件时,若文件在镜像层,其实是在容器层创建一个特殊的 writeout 文件,容器层访问不到,并没有实际删掉。

  • 目录重命名:目前 AUFS 还不支持目录重命名。

  
OverlayFS

OverlayFS 简介

  OverlayFS 是一种类似 AUFS 的现代联合文件系统,但实现更简单,性能更优。OverlayFS 严格说来是 Linux 内核的一种文件系统,对应的 Docker 存储驱动为 Overlay 或者 Overlay2,Overlay2 需 Linux 内核 4.0 及以上,Overlay 需内核 3.18 及以上。且目前仅 Docker 社区版支持。条件许可的话,尽量使用 Overlay2,与 Overlay 相比,它的 inode 利用率更高。

容器如何使用 Overlay/Overlay2 读写文件

读文件

读文件存在以下三种场景:

  • 文件不存在容器层:若容器要读的文件不在容器层,会继续从底层的镜像层找

  • 文件仅在容器层:若容器要读的文件在容器层,直接读取,不用在底层的镜像层查找

  • 文件同时在容器层和镜像层:若容器要读的文件在容器层和镜像层中都存在,则从容器层读取

修改文件或目录

写文件存在以下三种场景:

  • 首次写文件:若要写的文件位于镜像层中,则执行 copy_up 将文件从镜像层拷贝至容器层,然后进行修改,并在容器层保存一份新的。若文件较大,效率较低。OverlayFS 工作在文件级别而不是块级别,这意味着即使对文件稍作修改且文件很大,也须将整个文件拷贝至容器层进行修改。但需注意的是,copy_up 操作仅发生在首次,后续对同一文件进行修改,操作容器层文件即可

  • 删除文件或目录:容器中删除文件或目录时,其实是在容器中创建了一个 writeout 文件,并没有真的删除文件,只是使其对用户不可见

  • 目录重命名:仅当源路径与目标路径都在容器层时,调用 rename(2) 函数才成功,否则返回 EXDEV

远程镜像仓库如何存储镜像?

  不少人可能经常使用 Docker,那么有没有思考过镜像推送至远程镜像仓库,是如何保存的呢?Docker 客户端是如何与远程镜像仓库交互的呢?

  我们平时本地安装的 Docker 其实包含两部分:docker client 与 docker engine,docker client 与 docker engine 间通过 API 进行通信。Docker engine 提供的 API 大致有认证、容器、镜像、网络、卷、swarm 等,具体调用形式请参考:Docker Engine API(https://docs.docker.com/engine/api/v1.27/#)。

  Docker engine 与 registry (即:远程镜像仓库)的通信也有一套完整的 API,大致包含 pull、push 镜像所涉及的认证、授权、镜像存储等相关流程,具体请参考:Registry API(https://github.com/docker/distribution/blob/master/docs/spec/api.md)。目前常用 Registry 版本为 v2,Registry v2 拥有断点续传、并发拉取镜像多层等特点。能并发拉取多层是因为镜像的元信息与镜像层数据分开存储,当 pull 一个镜像时,先进行认证获取到 token 并授权通过,然后获取镜像的 manifest 文件,进行 signature 校验。校验完成后,依据 manifest 里的层信息并发拉取各层。其中 manifest 包含的信息有:仓库名称、tag、镜像层 digest 等, 更多,请参考:manifest 格式文档(https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md)。

  各层拉下来后,也会先在本地进行校验,校验算法采用 sha256。Push 过程则先将镜像各层并发推至 Registry,推送完成后,再将镜像的 manifest 推至 Registry。Registry 其实并不负责具体的存储工作,具体存储介质根据使用方来定,Registry 只是提供一套标准的存储驱动接口,具体存储驱动实现由使用方实现。

  目前官方 Registry 默认提供的存储驱动包括:微软 Azure、Google gcs、Amazon s3、OpenStack swift、阿里云 OSS、本地存储等。若需要使用自己的对象存储服务,则需要自行实现 registry 存储驱动。网易云目前将镜像存储在自己的对象存储服务 nos 上,故专门针对 nos 实现了一套存储驱动,另外认证服务也对接了网易云认证服务,并结合自身业务实现了一套认证、授权逻辑,并有效地限制了仓库配额。

  Registry 干的事情其实很简单,大致可分为:① 读配置 ;② 注册 handler ;③ 监听。本质上 Registry 是个 HTTP 服务,启动后,监听在配置文件设定的某端口上。当 http 请求过来后,便会触发之前注册过的 Handler。Handler 包含 manifest、tag、blob、blob-upload、blob-upload-chunk、catalog 等六类,具体请可参考 Registry 源码: /registry/handlers/app.go:92。配置文件包含监听端口、auth 地址、存储驱动信息、回调通知等。

从开发到部署会用到的 Docker 命令

本文的目的是理解容器开发在目标环境中部署的端到端流程,并列出这些操作所需的 Docker 命令。

一、介绍

整个流程包括使用代码、依赖软件和配置来开发容器映像,在开发环境中运行和测试容器,将容器映像发布到 Docker Hub,以及最后的部署和在目标环境中运行容器。

本文假设您已经在开发和目标环境中安装了 Docker 引擎。有关安装说明请参阅 6.3:https://docs.docker.com/engine/installation/。

二、开发容器映像

在构建容器映像之前,你需要创建一个 dockerfile,它包含了所需要的信息。请参考https://nodejs.org/en/docs/guides/nodejs-docker-webapp/来编制一个 dockerfile。

2.1 构建 Docker 容器

未分类

这个命令会使用当前目录下的 Dockerfile。如果 dockerfile 使用了其它文件名或者放在其它位置,可以使用 -f 参数来指定 dockerfile 的名称。“docker build” 命令会构建容器映像,这个容器映像的名称由 “-t” 参数指定。

未分类

未分类

2.2 Docker 映像命名规范

如果你只是在本地使用,那么你可以随意为 Docker 容器命名。它可以像上面那边简单的命名为“myApp”。但是如果你想将映像发布到 Docker Hub,就需要遵循特定的命名规范。这个规范有助于 Docker 工具将容器映像发布到正确的命名空间和仓库。

格式如下:

未分类

现在我们按上面的规范来构建 Docker 映像:

未分类

我们可以使用“docker tag”命令从已经存在的映像创建新的映像。“docker tag”命令会在下面说明。

2.3 列出 Docker 中所有映像

未分类

未分类

三、运行容器

3.1 启动 Docker 容器

使用“docker run”命令来启动 Docker 容器。

未分类

未分类

“-d” 参数会让容器以独立的模式来运行容器,这样即使终端关闭了容器仍然会保持运行。

“-p” 用于映射容器。比如,“-p 8080:8080” 的第一个端口号用在 Docker 主机上,第二个端口号是在 Docker 容器中使用的。根据这个参数的设置,所有对 Docker 主机端口的数据传输都会被转发到对应的 Docker 容器端口。

3.2 查看运行中的容器

未分类

未分类

从上面的输出我们可以看到 Docker 容器以 “trusting_snyder” 这个名字在运行。

如果要列出所有容器,而不管其状态如何,使用 “-a” 参数。

未分类

3.3 显示运行中容器的控制台日志

未分类

未分类

ContainerName(容器名称) 可以通过 “docker ps” 命令找到。

3.4 登入容器

未分类

上面的命令会用容器中的 “bash” shell 给出提示。

未分类

3.5 停止容器运行

未分类

未分类

3.6 从 Docker 中删除容器映像

未分类

使用 “docker images” 或 “docker images -a” 命令找到 imageId(映像 ID)。

未分类

未分类

上面的命令会强制删除指定的映像。

3.7 清理 Docker / 删除本地 Docker 中所有容器映像

未分类

四、发布容器映像

Docker 容器映像可以发布到本地库或公共的 Docker Hub。两种情况所使用的命令的操作过程一样。为了将你的 Docker 映像发布到 Docker Hub,你得先在 http://hub.docker.com 创建自己的命名空间和仓库。

我自己的命名空间是 “saravasu”,当前练习使用的仓库是 “techietweak”。

未分类

4.1 登录 Docker Hub

未分类

如果你想登录本地库,请先提供 URL。如果没有指定 URL,那么这个命令会登录 Docker Hub。

未分类

未分类

4.2 标记容器映像

在把容器映像推送到 Docker Hub 之前,必须按指定的格式对其进行标记:/:。如果你没有指定版本(version),它会使用默认的 “default”。下面的命令演示了标记映像:

未分类

4.3 将 Docker 映像推送到 Docker Hub

未分类

未分类

4.4 在 Docker Hub 中检查容器映像

现在使用你的账号登录 Docker Hub 并检查映像的仓库中进行检查。

docker+jenkins+seneca构建去集中化微服务架构

前言

在微服务架构中,服务发现一直是一件比较复杂的事。而且服务发现式的架构处理不好,容易产生集中化。同时,微服务的提供,不可避免的需要一些负载均衡方案,实现服务的高可用和可扩展,这无疑增加了很多复杂度。

笔者认为,使用异步、基于消息的方式,可能更适合微服务架构。

基于消息的微服务架构,对于所有微服务的部署条件非常简单,只需要能访问到消息服务即可。同时微服务节点的移除和增加不会影响到服务的提供。相比服务发现的架构,简单太多了,简单即是美。

在这次实践中,使用到了seneca,一个nodejs 微服务框架。seneca,使用seneca-amqp-transport插件,可以轻松构建基于消息的微服务。

下面是架构图:

未分类

https://www.processon.com/view/link/59dc2491e4b0ef561379bc25
在这个架构中,我们使用的是标准的seneca定义的命令规范,这可能是所有微服务都需要遵守的一个规范,至于说使用其他语言,也很简单。封装一个seneca命令规范的库即可。不知道官方有没开发,开发起来难度也不会太大。

接口层比较灵活,可以根据上层应用特性,来决定如何封装传输协议,最后将转化成标准命令发送到消息服务。不建议直接访问消息服务,上层应用应保持灵活。

完整的实践代码:https://github.com/luaxlou/micro-service-practice.git

一、前期准备

使用docker-machine创建虚拟机。

关于docker的一些基本用法,可以读上一篇文章:docker+consul基于服务发现的极简web架构实践,这里就不再赘述。

依次创建3台虚拟机:

$ dm create -d "virtualbox” node1
$ dm create -d "virtualbox” node2
$ dm create -d "virtualbox" node3

二、开始构建

1、搭建Rabbitmq消息服务

消息队列服务,已经成为高并发应用的必备基础服务。我们选用Rabbitmq,你可以换成任意的,遵循amqp协议即可。

使用docker安装很方便,但是生产环境不建议使用docker安装。更推荐的是使用云服务,这样能保证足够高的高可用和扩展性。虽然价格贵点,但是这是唯一的单点,花点钱还是值得的。

直接安装在宿主机上:

$ docker search rabbitmq

NAME                                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
rabbitmq                                   RabbitMQ is an open source multi-protocol ...   1466      [OK]
tutum/rabbitmq                             Base docker image to run a RabbitMQ server      11
frodenas/rabbitmq                          A Docker Image for RabbitMQ                     11                   [OK]
sysrun/rpi-rabbitmq                        RabbitMQ Container for the Raspberry Pi 2 ...   6
aweber/rabbitmq-autocluster                RabbitMQ with the Autocluster Plugin            5
gonkulatorlabs/rabbitmq                    DEPRECATED: See maryville/rabbitmq              5                    [OK]
letsxo/rabbitmq                            RabbitMQ with Management and MQTT plugins.      4                    [OK]
bitnami/rabbitmq                           Bitnami Docker Image for RabbitMQ               3                    [OK]
$ docker run -d --name rabbit -p   5672:5672  rabbitmq

这样就启动了一个消息队里服务,并且开放5672端口

2、安装jenkins

jenkins用于自动集成,不然每次构建是个很麻烦的事。

下面的实践是笔者掉了不少坑之后完成的,jenkins在安装过程中会有不少麻烦,而且在mac下安装也会遇到麻烦。

将jenkins 安装到 node1

$ dm ssh node1

$ mkdir /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /var/run/docker.sock

$ docker run -d -v /var/run/docker.sock:/var/run/docker.sock 
                -v /mnt/sda1/var/jenkins_home:/var/jenkins_home 
                -v $(which docker):/usr/bin/docker -p 8080:8080 jenkins

查看初始密码:

$ cat /mnt/sda1/var/jenkins_home/secrets/initialAdminPassword

3、安装私有的Registry

在mac上安装即可

$ docker run -d -p 5000:5000 registry

文档参考:https://docs.docker.com/registry/spec/api/

4、准备代码

代码使用的是seneca官方的例子,完整的Dockerfile也已经写好。

FROM node:alpine

RUN npm install pm2 -g
WORKDIR /usr/src/app

COPY package.json ./
RUN npm install
COPY . .

CMD ["pm2-docker","process.yml"]

为了让nodejs能使用到多核cpu,Dockerfile 集成了pm2,使用pm2来管理node进程。

完整代码:
https://github.com/luaxlou/micro-service-practice.git

5、配置自动集成

这里使用了最新版的jenkins,新版的jenkins使用了pipline。一种新的构建方式,使用groovy语法。

写起来是挺优雅的,但是学习成本颇高。因为文档不全及有些文档失效,笔者不得已反编译了pipeline插件,才得以调通。

使用pipeline script

node {
    stage('Preparation') {
        def r = git('https://github.com/luaxlou/micro-service-practice.git')
   }
   stage('Build') {
       dir('seneca-listener') {
          withEnv(["DOCKER_REGISTRY_URL=http://192.168.99.1:5000"]) {

              docker.build("seneca-listener").push("latest")

          }

       }

   }

}

开始构建,顺利的话,会看到如下的结果:

未分类

这是pipeline的特性,可以可视化看到各个阶段的执行情况,算是不小的进步吧。

访问私有Registy的API,就可以看到生成的tag。

curl http://192.168.99.1:5000/v2/seneca-listener/tags/list

6、最后一步,试试我们的程序

在宿主机发布消息:

$ git clone https://github.com/luaxlou/micro-service-practice.git

seneca-clinet 代码是接口层代码的示意,可以根据自己的喜好封装。
同时直接发送了命令代码用于测试。

进入seneca-clinet 目录

$  AMQP_URL=192.168.99.1:5672 node index.js

这个程序会每隔两秒发送一个命令:

#!/usr/bin/env node
'use strict';

const client = require('seneca')()
    .use('seneca-amqp-transport')
    .client({
        type: 'amqp',
        pin: 'cmd:salute',
        url: process.env.AMQP_URL
    });

setInterval(function() {
    client.act('cmd:salute', {
        name: 'World',
        max: 100,
        min: 25
    }, (err, res) => {
        if (err) {
            throw err;
        }
        console.log(res);
});
}, 2000);

虽然一直在发命令,你很快就会发现命令全部超时了。这是因为还没有消费者,当然这些命令也没有丢失,只不过接口层没有得到及时返回。如果应用层支持异步的模式,每个command都有独立的id,可以保留id后,以后再过来取。这就很灵活了,一切看需求去封装接口层即可。

进入node2

$ docker run 192.168.99.1:5000/seneca-listener:latest
0|seneca-l | {"kind":"notice","notice":"hello seneca fwunhukrcmzn/1507605332382/16/3.4.2/-","level":"info","seneca":"fwunhukrcmzn/1507605332382/16/3.4.2/-","when":1507605332661}

启动后,回到seneca-clinet,发现之前超时的命令,全部接收到了。

{ id: 86,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332699 }
{ id: 44,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332701 }
{ id: 56,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332703 }
{ id: 57,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332706 }
{ id: 58,
  message: 'Hello World!',
  from: { pid: 16, file: 'index.js' },
  now: 1507605332707 }

至此,完整架构已经构建完毕。

7、一些未完的事项

  1. 自动集成,只需要配置webhook即可。

  2. 自动部署,因为docker运转的方式,当服务升级时需要重启docker进程。方式有很多,比较粗暴的是直接控制宿主机,或者类似salt这样的工具。

目前来说,没有找到太好的开源方案。个人倾向于自己开发agent,发布有限的API,用于常规的部署或者其他任务,以及可以定时收集服务器的信息,用于监控。这可能会是笔者的下一个开源项目。

三、总结

这篇文章算是一个新的里程碑,实践的成果将用于后期的架构。docker让我从传统的架构模式中脱离出来,同时也让我吃了不少苦头。但这一切都是值得的。

同时也是一个新的开始,终于从之前的公司出来。未来何去何从,有很多的未知,但我相信都是美好的。

这也许就是人生的魅力。

Hello World!!

使用 Docker 搭建 Laravel 本地环境

Laravel 官方提供 Homestead 和 Valet 作为本地开发环境,Homestead 是一个官方预封装的 Vagrant Box,也就是一个虚拟机,但是跟 docker 比,它占用体积太大,启动速度慢,同时响应速度很慢,现在有了 docker 这种更好的方式,可以轻松方便的搭建整套 PHP 开发环境。

本文就介绍如何使用 docker 搭建 Laravel 本地环境。

安装 docker

首先安装 docker。

克隆 laradock

laradock 官方文档:http://laradock.io/

laradock github:https://github.com/laradock/laradock

laradock 是一个包含全功能用于 docker 的 PHP 运行环境,使用 docker-compose 方式部署。(特别说明:它不仅用于 Laravel 环境搭建,而且支持所有其他 PHP 框架,它就是一整套 PHP 的环境。)

部署 PHP 环境

1. 克隆 laradock

git clone https://github.com/Laradock/laradock.git

2. 创建环境变量文件

cp env-example .env

3. 直接用 docker-compose 运行需要启用的服务,如:

docker-compose up -d nginx mysql redis beanstalkd

这样就启动了所需的 PHP 运行环境,php-fpm 默认会运行,所以不需要指定。

未分类

Laravel 配置文件

Laravel 配置文件需要注意的问题是,在 .env 文件中,mysql 和 redis 的地址需填写成这样,而不是 ip 地址形式:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=tanteng.me
DB_USERNAME=root
DB_PASSWORD=root

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

注意代码中高亮部分。

Nginx 配置

在本地通过域名方式访问站点,要将 host 中域名绑定到本地,同时还需要增加 nginx 配置。

未分类

如图,在 laradock 项目的 nginx 文件夹下的 sites 目录下添加配置文件即可。

执行 composer

执行 composer 等操作,需要进入到 workspace 容器中进行,使用命令:

docker-compose exec workspace bash

进入到 workspace 容器,就可以进行 compose 命令等操作了。

具体使用上的问题请参加 laradock 官方文档,上面都有说明。