如何编写 Dockerfile 文件创建 Docker 镜像

一、前言

承接上篇文章 docker 镜像与容器,本篇来讲讲如何创建 Dockerfile 来构建一个镜像。上篇文章有讲到构建一个自定义镜像是手动去构建的,虽然步骤清晰,但是操作比较繁琐,镜像分发起来也不是很方便,所以有必要用一种更好的办法去替换这种模式去创建自定义镜像,于是 Dockerfile 就是最优替代方案。废话少说,现在就来看看如何编写一个 Dockerfile 文件并创建容器镜像的,先说明一个本篇文章的运行环境吧,有看过上篇文章的朋友应该知道,我用的 docker 的镜像加速地址是阿里云的,我觉得这是我用 docker 最无痛的环境了。

二、Dockerfile 示例

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER lorenwe 

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  文件放在当前目录下,拷过去会自动解压
ADD nginx-1.13.7.tar.gz /tmp/

#RUN 执行以下命令
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 
    && yum update -y 
    && yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel 
    && yum clean all 
    && rm -rf /usr/local/src/*
RUN useradd -s /sbin/nologin -M www

#WORKDIR 相当于cd
WORKDIR /tmp/nginx-1.13.7

RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install

RUN cd / && rm -rf /tmp/

COPY nginx.conf /usr/local/nginx/conf/

#EXPOSE 映射端口
EXPOSE 80 443

#ENTRYPOINT 运行以下命令
ENTRYPOINT ["nginx"]

#CMD 运行以下命令
CMD ["-h"]

以上代码示例是我编写的一个认为很有代表性的 dockerfile 文件,涉及到的内容不多,但基本上把所有 dockerfile 指令都用上了,也包含一些细节方面的东西,为了达到示例的效果所以并不是最简洁的 dockerfile,建立一个文件夹将以上 dockerfile 放在该文件内,再去 nginx 官网把 nginx 源码包下来放到该文件夹内,之后再在该文件夹内打开命令行窗口,最好是以管理员权限打开命令行窗口,以免出现一些权限问题的错误,此时的目录结构应该是以下样子的

未分类

三、指令分析

FROM 表示的是这个 dockerfile 构建镜像的基础镜像是什么,有点像代码里面类的继承那样的关系,基础镜像所拥有的功能在新构建出来的镜像中也是存在的,一般用作于基础镜像都是最干净的没有经过任何三方修改过的,比如我用的就是最基础的 centos,这里有必要说明一下,因为我用的镜像加速源是阿里云的,所以我 pull 下来的 centos 是自带阿里云的 yum 源的镜像,如果你用的不是阿里云的镜像加速源,pull 下来的镜像 yum 源也不一样,到时 yum 安装软件的时候可能会遇到很多问题(你懂得)。

MAINTAINER 就是维护者信息了,填自己名字就可了,不用说什么了

ENV 设置环境变量,简单点说就是设置这个能够帮助系统找到所需要运行的软件,比如我上面写的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,这句话的意思就是告诉系统如果运行一个没有指定路径的程序时可以从 /usr/local/nginx/sbin 这个路径里面找,只有设置了这个,后面才可以直接使用 ngixn 命令来启动 nginx,不然的话系统会提示找不到应用。

ADD 顾名思义,就是添加文件的功能了,但是他比普通的添加做的事情多一点,源文件可以是一个文件,或者是一个 URL 都行,如果源文件是一个压缩包,在构建镜像的时候会自动的把压缩包解压开来,示例我写的是 ‘ADD nginx-1.13.7.tar.gz /tmp/’ 其中 nginx-1.13.7.tar.gz 这个压缩包是必须要在 dockefile 文件目录内的,不在 dockerfile 文件目录内的 比如你写完整路径 D:test/nginx-1.13.7.tar.gz 都是会提示找不到文件的。

RUN 就是执行命令的意思了,RUN 可以执行多条命令, 用 && 隔开就行,如果命令太长要换行的话在末尾加上 ‘’ 就可以换行命令,RUN 的含义非常简单,就是执行命令,但其中有一些细节还是需要注意的,现在就通过上面的示例来看看需要注意的地方有哪些吧。其中 RUN rpm –import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 的作用就是导入软件包签名来验证软件包是否被修改过了,为做到安全除了系统要官方的之外软件也要保证是可信的。yum update -y 升级所有包,改变软件设置和系统设置,系统版本内核都升级,我们知道 linux 的软件存在依赖关系,有时我们安装新的软件他所依赖的工具软件也需要是最新的,如果没有用这个命令去更新原来的软件包,就很容易造成我们新安装上的软件出问题,报错提示不明显的情况下我们更是难找到问题了,为避免此类情况发生我们还是先更新一下软件包和系统,虽然这会使 docker 构建镜像时变慢但也是值得的,至于后面的命令自然是安装各种工具库了,接着来看这句 yum clean all ,把所有的 yum 缓存清掉,这可以减少构建出来的镜像大小,rm -rf /usr/local/src/ 清除用户源码文件,都是起到减少构建镜像大小的作用。RUN 指令是可以分步写的,比如上面的 RUN 可以拆成以下这样:

# 不推荐
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 
RUN yum update -y 
RUN yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel 
RUN yum clean all 
RUN rm -rf /usr/local/src/*

这样也是可以的,但是最好不要这样,因为 dockerfile 构建镜像时每执行一个关键指令都会去创建一个镜像版本,这有点像 git 的版本管理,比如执行完第一个 RUN 命令后在执行第二个 RUN 命令时是会在一个新的镜像版本中执行,这会导致 yum clean all 这个命令失效,没有起到精简镜像的作用,虽然不推荐多写几个 RUN,但也不是说把所有的操作都放在一个 RUN 里面,这里有个原则就是把所有相关的操作都放在同一个 RUN 里面,就比如我把 yum 更新,安装工具库,清除缓存放在一个 RUN 里面,后面的编译安装 nginx 放在另外一个 RUN 里面。

WORKDIR 表示镜像活动目录变换到指定目录,就相当于 linux 里面 cd 到指定目录一样,其实完全没有必要使用这个指令的,在需要时可以直接使用 cd 命令就行,因为这里使用了 WORKDIR,所以后面的 RUN 编译安装 nginx 不用切换目录,讲到这里又想起了另外一个问题,如下:

RUN cd /tmp/nginx-1.13.7

RUN ./configure

RUN ./configure这样可不可以呢,我想前面看懂的朋友应该知道答案了吧,这里还是再啰嗦一下,这样是会报找不到 configure 文件错误的,原因很简单,因为这个两个命令都不是在同一个镜像中执行的,第一个镜像 cd 进入的目录并不代表后面的镜像也进入了。
COPY 这个指令很简单,就是把文件拷贝到镜像中的某个目录,注意源文件也是需要在 dockerfile 所在目录的,示例的意思是拷贝一份 nginx 配置文件,现在就在 dockerfile 所在目录创建这个文件

user  www;
worker_processes  2;
daemon off;

pid        logs/nginx.pid;

events {    
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

配置很简单,就是对官方的配置文件把注释去掉了,注意里面的 daemon off; 配置,意思是关闭 nginx 后台运行,原因在上一篇文章中讲过,这里再来絮叨一下,容器默认会把容器内部第一个进程是否活动作为docker容器是否正在运行的依据,如果 docker 容器运行完就退出了,那么docker容器便会直接退出,docker run 的时候把 command 作为容器内部命令,如果使用 nginx,那么 nginx 程序将后台运行,这个时候 nginx 并不是第一个执行的程序,而是执行的 bash,这个 bash 执行了 nginx 指令后就挂了,所以容器也就退出了,如果我们设置了 daemon off 后启动 nginx 那么 nginx 就会一直占用命令窗口,自然 bash 没法退出了所以容器一直保持活动状态。

EXPOSE 示例注释写的是映射端口,但我觉得用暴露端口来形容更合适,因为在使用 dockerfile 创建容器的时候不会映射任何端口,映射端口是在用 docker run 的时候来指定映射的端口,比如我把容器的 80 端口映射到本机的 8080 端口,要映射成功就要先把端口暴露出来,有点类似于防火墙的功能,把部分端口打开。

ENTRYPOINT 和 CMD 要放在一起来说,这两者的功能都类似,但又有相对独特的地方,他们的作用都是让镜像在创建容器时运行里面的命令。当然前提是这个镜像是使用这个 dockerfile 构建的,也就是说在执行 docker run 时 ENTRYPOINT 和 CMD 里面的命令是会执行的,两者是可以单独使用,并不一定要同时存在,当然这两者还是有区别的。

先从 CMD 说吧,CMD 的一个特点就是可被覆盖,比如把之前的 dockerfile 的 ENTRYPOINT 这一行删除,留下 CMD 填写[“nginx”],构建好镜像后直接使用 docker run lorenwe/centos_nginx 命令执行的话通过 docker ps 可以看到容器正常运行了,启动命令也是 “ngixn”,但是我们使用 docker run lorenwe/centos_nginx bin/bash 来启动的话通过 docker ps 查看到启动命令变成了 bin/bash,这就说明了 dockerfile 的 CMD 指令是可被覆盖的,也可以把他看做是容器启动的一个默认命令,可以手动修改的。

而 ENTRYPOINT 恰恰相反,他是不能被覆盖,也就是说指定了值后再启动容器时不管你后面写的什么 ENTRYPOINT 里面的命令一定会执行,通常 ENTRYPOINT 用法是为某个镜像指定必须运行的应用,例如我这里构建的是一个 centos_nginx 镜像,也就是说这个镜像只运行 ngixn,那么我就可以在 ENTRYPOINT 写上[“nginx”],有些人在构建自己的基础镜像时(基础镜像只安装了一些必要的库)就只有 CMD 并写上 [‘bin/bash’],当 ENTRYPOINT 和 CMD 都存在时 CMD 中的命令会以 ENTRYPOINT 中命令的参数形式来启动容器,例如上面的示例 dockerfile,在启动容器时会以命令为 nginx -h 来启动容器,遗憾的是这样不能保持容器运行,所以可以这样启动 docker run -it lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf,那么容器启动时运行的命令就是 nginx -c /usr/local/nginx/conf/nginx.conf,是不是很有意思,可以自定义启动参数了。

当然还有一些没有用到的指令:

ARG,ARG指令用以定义构建时需要的参数,比如可以在 dockerfile中写上这句 ARG a_nother_name=a_default_value,ARG指令定义的参数,在docker build命令中以 –build -arg a_name=a_value 形式赋值,这个用的一般比较少。

VOLUME,VOLUME指令创建一个可以从本地主机或其他容器挂载的挂载点,用法是比较多的,都知道 docker 做应用容器比较方便,其实 docker 也可做数据容器,创建数据容器镜像的 dockerfile 就主要是用 VOLUME 指令,要讲明 VOLUME 用法有必要在开一篇文章,再此就不做介绍了,
USER,USER用来切换运行属主身份的。docker 默认是使用 root 用户,但若不需要,建议切换使用者身分,毕竟 root 权限太大了,使用上有安全的风险。LABEL,定义一个 image 标签。

四、构建演示

dockerfile 构建镜像的命令很简单,在我的示例中我的命令是 “docker build -t lorenwe/centos_nginx . “,注意后面的点不能省略,表示的从当前目录中寻找 dockerfile 来构建镜像

D:dockerlorenwe>docker build -t lorenwe/centos_nginx .
Sending build context to Docker daemon  995.8kB
Step 1/13 : FROM centos
 ---> d123f4e55e12
Step 2/13 : MAINTAINER lorenwe
 ---> Running in e5c7274f50e8
 ---> 606f7222e69a
Removing intermediate container e5c7274f50e8
Step 3/13 : ENV PATH /usr/local/nginx/sbin:$PATH
 ---> Running in 23716b428809
 ---> 5d8ee1b5a899
         ....
Successfully built eaee6b40b151
Successfully tagged lorenwe/centos_nginx:latest

看到以上内容就说明成功,构建过程可能需要一点点时间,毕竟要安装一些软件,如果你跟我一样是配置的阿里云的容器源构建时应该不会出现什么问题,因为我之前是有拉取过 centos ,所以在 build 时直接使用本地的 centos,如果你没有拉取过 centos,那么在 build 时还会把 centos 拉取下来

D:dockerlorenwe>docker images
REPOSITORY               TAG     IMAGE ID       CREATED          SIZE
lorenwe/centos_nginx     latest  eaee6b40b151   7 minutes ago    427MB
lorenwe/centos_net_tools latest  35f8073cede1   6 days ago       277MB
centos                   latest  d123f4e55e12   3 weeks ago      197MB
d4w/nsenter              latest  9e4f13a0901e   14 months ago    83.8kB

D:dockerlorenwe>docker run -itd --name nginx1 lorenwe/centos_nginx
15d4f108dab7c2f276209ebeb501cac0d3be828e1e81bae22d3fd97c617439eb

D:dockerlorenwe>docker ps
CONTAINER ID    IMAGE    COMMAND     CREATED    STATUS     PORTS     NAMES

D:dockerlorenwe>docker ps -a
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS   PORTS   NAMES
15d4f108dab7   lorenwe/centos_nginx  "nginx -h"                            nginx1

D:dockerlorenwe>docker run -itd --name nginx2 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
b6b0e962ca3056d67c24145b08975ffddb9cc050fce5f09f65310fb323ffc1c3

D:dockerlorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND        CREATED    STATUS    PORTS     NAMES
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."              80/tcp    nginx2

D:dockerlorenwe>docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
2f6997745641e3e3edbbfe5213e6235cab3b5a929f116a2c132df504156090c6

D:dockerlorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS     PORTS                  NAMES
2f6997745641   lorenwe/centos_nginx  "nginx -c /usr/loc..."          0.0.0.0:8080->80/tcp   nginx3
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."          80/tcp                 nginx2

D:dockerlorenwe>docker stop nginx2
nginx2

其中 “docker run -itd -p 8080:80 –name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf” 中的 -p 8080:80 表示把主机的 8080 端口映射到容器的 80 端口,因为之前我们在 dockerfile 中把 80 端口暴露出来了,做好端口映射后现在就可以在主机中打开浏览器访问 127.0.0.1:8080 就能看到 nginx 的欢迎页面了 (^v^).

D:dockerlorenwe>docker run -itd -v D:/docker/lorenwe/html:/usr/local/nginx/html  -p 8081:80 --name nginx4 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
cd2d4eb70a39057aed3bfcb64e1f03433e2054d7ff5d50098f49d2e6f2d9e02e

我再在原来的参数中加入了 -v 参数,其作用就是把一个本地主机的目录挂载到容器内部,这个目录是一个共享的状态,两边都可以进行修改,这就是容器的共享卷,其作用就不言而喻了,现在我们在 D:dockerlorenwe 的目录下新建一个叫 html 的文件夹,再在 html 文件夹内新建一个 index.html 随便写上一点内容后再去主机浏览器上访问一下 127.0.0.1:8081 看看是不是你想要看到内容。虽然通过 -v 参数可以满足大部分应用场景,但是 docker 的 VOLUME 还有其他更好用法,欲知后事如何,请看下回分解!

docker离线安装

背景

最近在做私有化部署项目,项目的整体方案基于docker。考虑到客户的运行环境可能无法连接到公网,因此需要制作docker的离线安装。整个离线安装分为三个部分:1)准备docker离线安装包,2)docker离线源配置,3)离线安装docker

准备docker离线安装包

目前只考虑Linux及其发行版的服务器。docker对于机器和操作系统的要求:

  • 内核版本3.10及其以上
  • 操作系统位数为64位
  • CPU架构为x86_64或amd64(目前也有别的支持)
  • 内核开启并支持cgroup和命名空间

简单点说来,常用的 CentOS 7及其以上,Ubuntu 14及其以上,Fedora 24及其以上,Debian 8及其以上,还有 Raspbian 等。这个部分具体可以参考[1]。这个是docker官方的在线安装脚本,本文中的内容主要也是参考这个脚本。

对于不同的操作系统,不同的架构,需要的docker安装包不同,所以要分开处理。目前主流的包管理工具是 apt-get 和 yum,这两者也分别对应到UbuntuDebian和CentOSFedora系列操作系统。不管是哪种包管理工具,基本思路都是

  1. 下载docker安装包及其依赖
  2. 对下载下来的安装包制作本地源
  3. 准备本地源配置文件

对于 apt-get 来说,可以执行以下脚本,该脚本抽取自参考链接[1]。对于流程的解释可以参考链接[2]

#!/bin/sh

lsb_dist="ubuntu"
dist_version="xenial"   # 14-trusty 16-xenial 17-zesty

DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
DOWNLOAD_DIR="/home/work/docker-packages/$lsb_dist-$dist_version"

set -e
apt_repo="deb [arch=$(dpkg --print-architecture)] $DOWNLOAD_URL/linux/$lsb_dist $dist_version stable"

if [ ! -x "$DOWNLOAD_DIR" ]; then
    mkdir -p "$DOWNLOAD_DIR"
fi

apt-get update -qq >/dev/null
apt-get install -y -qq apt-transport-https ca-certificates curl dpkg-dev > /dev/null
curl -fsSL "$DOWNLOAD_URL/linux/$lsb_dist/gpg" | apt-key add - > /dev/null
echo "$apt_repo" > /etc/apt/sources.list.d/docker.list
if [ "$lsb_dist" = "debian" ] && [ "$dist_version" = "wheezy" ]; then
    sed -i "/deb-src.*download.docker/d" /etc/apt/sources.list.d/docker-ce.list
fi
# 只下载docker和依赖的安装包
apt-get update -qq >/dev/null
apt-get --download-only -o Dir::Cache="./" -o Dir::Cache::archives=$DOWNLOAD_DIR install -y --no-install-recommends docker-ce > /dev/null

# 为安装包建立索引,方便后续加载为本地源安装
touch $DOWNLOAD_DIR/Packages.gz
dpkg-scanpackages $DOWNLOAD_DIR /dev/null | gzip > $DOWNLOAD_DIR/Packages.gz

我在制作的时候,对于生成的Packages.gz,调整里面每一个软件的 Filename 项,只留下软件名,不保留前面的目录路径。具体可以使用sed命令。

对于 yum 来说,可以执行以下脚本,该脚本也抽取自参考链接[1]。对于流程的解释可以参考链接[3]

#!/bin/sh

lsb_dist="centos"
dist_version="7"

DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
DOWNLOAD_DIR="/home/work/docker-packages/$lsb_dist-$dist_version"

set -e
yum_repo="$DOWNLOAD_URL/linux/$lsb_dist/docker-ce.repo"

if [ ! -x "$DOWNLOAD_DIR" ]; then
    mkdir -p "$DOWNLOAD_DIR"
fi

# 只下载docker和依赖的安装包
yum-config-manager --add-repo $yum_repo
yum makecache
yum install --downloadonly --downloaddir=$DOWNLOAD_DIR docker-ce

# 为安装包建立索引,方便后续加载为本地源安装
createrepo $DOWNLOAD_DIR

准备docker离线源配置

将刚刚准备好的安装包内容($lsb_dist-$dist_version这个目录下)拷贝到目标机器上,比如统一放到/home/work/docker-packages目录下。基于这个路径:

对于 apt-get 系的离线源配置文件为 docker-ce.list,内容如下

deb [trusted=yes] file:/home/work/docker-packages ./

对于 yum 系的离线源配置文件为 docker-ce.repo,内容如下

[Local_docker_yum]
name=Local Docker Yum Repository
baseurl=file:///home/work/docker-packages/
enabled=1
gpgcheck=0

离线安装docker

对于 apt-get 将离线源配置文件 docker-ce.list 拷贝到 /etc/apt/sources.list.d 目录下。如果电脑不能联网,先将 /etc/apt/sources.list 文件重命名;然后执行apt-get update;再把刚刚重命名的 /etc/apt/sources.list 改回来。如果不这么改的话,在apt-get update的时候可能联网失败而无法完成更新。

最后执行 apt-get install docker-ce

对于 yum 将离线源配置文件 docker-ce.repo 拷贝到 /etc/yum.repos.d/ 目录下。如果电脑不能联网,先将 /etc/yum.repos.d/ 目录下其他配置文件重命名;然后执行 yum makecache;最后再把刚刚重命名的文件改回来。这么做的理由同上。

最后执行 yum install docker-ce

Docker2(docker仓库+cgroup)

一.Docker仓库

1.创建docker仓库

docekr load -i registry:2.3.1

2.Docker 官方已经把仓库封装为镜像,直接通过启动容器就可以部署完成仓库

 docker run -d --name registry -p 5000:5000 -v/opt/registry:/var/lib/registry registry:2.3.1

3.默认docker 仓库远程推送拉取需要TLS加密支持,走的是https协议,如需开启http方式,
需要做如下修改:

vim /etc/systemd/system/docker.service(此处docker.service文件是链接或者cp过来的)
ExecStart=/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
--bip 192.168.0.1/24 --insecure-registry 172.25.254.7:5000
 systemctl daemon-reload
 systemctl restart docker

4.将镜像放进仓库中(push)
未分类
未分类
5.从仓库中拉取镜像
未分类
未分类
6.仓库认证
仓库不能是公开的(不能说随便谁都可以进行push和pull),最好有用户和权限

(1)创建目录/opt/auth,并创建用户用户admin,密码为westos;多个用户时,只需追加就好,此例中追加了一个用户haha ,密码为redhat
未分类
(2)生成容器vm1

docker run -d -p 5000:5000 --name vm1 -v  /opt/registry:/var/lib/registry -v /opt/auth:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry
Realm" -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" registry:2.3.1

未分类
(3)若不登录,则不能push进仓库
未分类
未分类
(4)用户admin登录,push
未分类

二.cgroup ##(此例是在redhat6.5中做测试)

cgroups 实现了对资源的配额和度量.(此例中凡是需要/etc/init.d/cgconfig restart 均需跳出cgroup目录进行操作,否则会重启失败)

1.安装
未分类
未分类
2.memory 内存相关的限制,此例中限制在200M=20010241024B
未分类
(1)编写配置文件(x1为自定义名字,限制使用内存字节以及swap字节,总共200M),记得跳出cgroup目录,进行重启

vim /etc/cgconfig.conf

未分类
(2)测试,当申请300M文件时,只能分给200M
未分类
(3)例子

(3.1)memapp1 memapp2 本身开启他们并没有内存限制,我们的例子为限制memapp2,允许memapp1开启
未分类
未分类
(3.2)将两个小程序所有者改为用户haha
未分类
未分类
未分类
(3.3)我们从(3.1)中可看出,memapp1启动需要将近4096个内存页,所以为了简单起见,我们将内存限制设置为5000内存页(即500010244B)

vim /etc/cgconfig.conf

未分类
未分类
3.cpu 在 cgroup 中,并不能像硬件虚拟化方案一样能够定义 CPU 能力,但是能够定义 CPU 轮转的优先级,因此具有较高 CPU 优先级的进程会更可能得到 CPU 运算。 通过将参数写入 cpu.shares ,即可定义改 cgroup 的 CPU 优先级 – 这里是一个相对权重,而非绝对值

(1)没有cpu限制,可看到为99.9%
未分类
未分类
(2)有了内存限制,设置为100,接近原来1024的十分之一
未分类
未分类
(3)blkio block IO 相关的统计和限制,byte/operation 统计和限制 (IOPS 等),读写速度限制等,但是这里主要统计的都是同步 IO

(3.1)没有读取限制(安装iotop,用iotop命令查看读取速度)

dd if=/dev/vda of=/dev/null &

未分类
未分类
(3.2)读取限制
未分类
未分类
未分类
(4)冻结freezer

(4.1)本身存在一进程8924
未分类
(4.2)vim /etc/cgconfig
未分类
(4.3)若freezer.state状态为FROZEN,则冻结该进程
未分类
未分类
(4.4)若修改为THAWED,则恢复该进程
未分类
未分类

使用docker-compose一键部署分布式文件系统FastDFS

什么是FastDFS

百度百科:FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

FastDFS是由余庆所开发的开源、免费的分布式文件系统,GitHub项目地址请点这里,其基本架构图如下:

未分类

FastDFS仅提供源码包,必须从源码编译安装,且其提供了nginx插件,也必须与nginx源码一起编译安装nginx,安装过程较为繁复。为快速搭建简易的开发、测试环境,我采用docker方式将源码下载、编译、配置过程完全自动化,采用docker-compose来组织docker容器,并采用一台tracker+一台storage的最小系统架构,且部署在一个容器内,这样就以最小的系统代价、最快的速度搭建起fastdfs使用环境了。标题中的”一键”稍显夸张,但事实上下载GitHub项目文件到本地后,只需两三条命令即可搞定一个最简单的FastDFS环境搭建。

**关于docker及docker-compose的安装,请见《Ubuntu安装docker和docker-compose》。&&

创建Dockerfile

本DockerFile采用的软件版本:

  • fastdfs:5.11
  • libfastcommon:1.0.36
  • fastdfs-nginx-module: 最新版(截至2017/11/28,为1.20)
  • nginx: 1.12.2

各源码均为在线下载。

DockerFile内容如下:

# 使用超小的Linux镜像alpine
FROM alpine:3.6

MAINTAINER YoungCoding <[email protected]>

ENV HOME /root

# 安装准备
RUN    apk update 
        && apk add --no-cache --virtual .build-deps bash gcc libc-dev make openssl-dev pcre-dev zlib-dev linux-headers curl gnupg libxslt-dev gd-dev geoip-dev

# 下载fastdfs、libfastcommon、nginx插件的源码
RUN     cd /root 
        && curl -fSL https://github.com/happyfish100/libfastcommon/archive/V1.0.36.tar.gz -o fastcommon.tar.gz 
        && curl -fSL  https://codeload.github.com/happyfish100/fastdfs/tar.gz/V5.11 -o fastfs.tar.gz 
        && curl -fSL  https://github.com/happyfish100/fastdfs-nginx-module/archive/master.tar.gz -o nginx-module.tar.gz 
        && tar zxf fastcommon.tar.gz 
        && tar zxf fastfs.tar.gz 
        && tar zxf nginx-module.tar.gz

# 安装libfastcommon
RUN     cd ${HOME}/libfastcommon-1.0.36/ 
        && ./make.sh 
        && ./make.sh install

# 安装fastdfs v5.11
RUN     cd ${HOME}/fastdfs-5.11/ 
        && ./make.sh 
        && ./make.sh install

# 配置fastdfs: base_dir
RUN     cd /etc/fdfs/ 
        && cp storage.conf.sample storage.conf 
        && cp tracker.conf.sample tracker.conf 
        && cp client.conf.sample client.conf 
        && sed -i "s|/home/yuqing/fastdfs|/var/local/fdfs/tracker|g" /etc/fdfs/tracker.conf 
        && sed -i "s|/home/yuqing/fastdfs|/var/local/fdfs/storage|g" /etc/fdfs/storage.conf 
        && sed -i "s|/home/yuqing/fastdfs|/var/local/fdfs/storage|g" /etc/fdfs/client.conf 

# 获取nginx源码,与fastdfs插件一起编译
RUN     cd ${HOME} 
        && curl -fSL http://nginx.org/download/nginx-1.12.2.tar.gz -o nginx-1.12.2.tar.gz 
        && tar zxf nginx-1.12.2.tar.gz 
        && chmod u+x ${HOME}/fastdfs-nginx-module-master/src/config 
        && cd nginx-1.12.2 
        && ./configure --add-module=${HOME}/fastdfs-nginx-module-master/src 
        && make && make install

# 设置nginx和fastdfs联合环境,并配置nginx
RUN     cp ${HOME}/fastdfs-nginx-module-master/src/mod_fastdfs.conf /etc/fdfs/ 
        && sed -i "s|^store_path0.*$|store_path0=/var/local/fdfs/storage|g" /etc/fdfs/mod_fastdfs.conf 
        && sed -i "s|^url_have_group_name =.*$|url_have_group_name = true|g" /etc/fdfs/mod_fastdfs.conf 
        && cd ${HOME}/fastdfs-5.11/conf/ 
        && cp http.conf mime.types anti-steal.jpg /etc/fdfs/ 
        && echo -e "
events {n
    worker_connections  1024;n
}n
http {n
    include       mime.types;n
    default_type  application/octet-stream;n
    server {n
        listen 8888;n
        server_name localhost;n

        location ~ /group[0-9]/M00 {n
            ngx_fastdfs_module;n
        }n
    }n
}">/usr/local/nginx/conf/nginx.conf

# 清理文件
RUN rm -rf ${HOME}/*
RUN apk del .build-deps gcc libc-dev make openssl-dev linux-headers curl gnupg libxslt-dev gd-dev geoip-dev
RUN apk add bash pcre-dev zlib-dev


# 配置启动脚本,在启动时中根据环境变量替换nginx端口、fastdfs端口
# 默认nginx端口
ENV WEB_PORT 8888
# 默认fastdfs端口
ENV FDFS_PORT 22122
# 创建启动脚本
RUN     echo -e "
mkdir -p /var/local/fdfs/storage/data /var/local/fdfs/tracker; n
ln -s /var/local/fdfs/storage/data/ /var/local/fdfs/storage/data/M00; nn
sed -i "s/listen .*$/listen $WEB_PORT;/g" /usr/local/nginx/conf/nginx.conf; n
sed -i "s/http.server_port=.*$/http.server_port=$WEB_PORT/g" /etc/fdfs/storage.conf; nn
if [ "$IP" = "" ]; then n
    IP=`ifconfig eth0 | grep inet | awk '{print $2}'| awk -F: '{print $2}'`; n
fi n
sed -i "s/^tracker_server=.*$/tracker_server=$IP:$FDFS_PORT/g" /etc/fdfs/client.conf; n
sed -i "s/^tracker_server=.*$/tracker_server=$IP:$FDFS_PORT/g" /etc/fdfs/storage.conf; n
sed -i "s/^tracker_server=.*$/tracker_server=$IP:$FDFS_PORT/g" /etc/fdfs/mod_fastdfs.conf; nn
/etc/init.d/fdfs_trackerd start; n
/etc/init.d/fdfs_storaged start; n
/usr/local/nginx/sbin/nginx; n
tail -f /usr/local/nginx/logs/access.log 
">/start.sh 
&& chmod u+x /start.sh

# 暴露端口。改为采用host网络,不需要单独暴露端口
# EXPOSE 80 22122

ENTRYPOINT ["/bin/bash","/start.sh"]

docker-compose.yaml

在DockerFile所在目录创建docker-compose.yaml,内容如下:

version: '3.0'

services:
    fastdfs:
        build: .
        image: youngcoding/fastdfs:5.11
        # 该容器是否需要开机启动+自动重启。若需要,则取消注释。
        #restart: always
        container_name: fastdfs
        environment:
            # nginx服务端口
            - WEB_PORT=8888
            # docker所在主机的IP地址
            - IP=192.168.56.110
        volumes:
            # 将本地目录映射到docker容器内的fastdfs数据存储目录,将fastdfs文件存储到主机上,以免每次重建docker容器,之前存储的文件就丢失了。
            - ${HOME}/docker-data/fdfs:/var/local/fdfs
        # 使docker具有root权限以读写主机上的目录
        privileged: true
        # 网络模式为host,即直接使用主机的网络接口
        network_mode: "host"

准备

$ cd <保存Dockerfile文件的路径>
# 检查文件夹下各文件是否齐全
$ ls
docker-compose.yaml     Dockerfile      log.sh

# 确保本机供挂载的存储文件夹存在,如不存在,则创建。注意,此文件夹需与docker-compose.yaml中设置的volume挂载目录一致,可以自行更改到其他文件夹。
$ mkdir -p ${HOME}/docker-data/fdfs

启动

$ docker-compose up -d
# 因为要下载软件包和源码,并编译,所以过程比较漫长,期间可能会出现红字的warning,不必理会。若报错,请根据提示排查。
...
Successfully built 4baafa5d2e75
Successfully tagged youngcoding/fastdfs:5.11
WARNING: Image for service fastdfs was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating fastdfs ...
Creating fastdfs ... done

# 查看容器运行状态
$ docker ps
或 $ docker-compose ps
CONTAINER ID        IMAGE                       COMMAND                 CREATED             STATUS              PORTS               NAMES
2a294bc410bd        youngcoding/fastdfs:5.11    "/bin/bash /start.sh"   4 minutes ago       Up 4 minutes                            fastdfs

测试

# 进入docker容器内终端
$ docker exec -it fastdfs或ID的前几位 /bin/bash

# 在容器内部终端执行上传测试
bash-4.3$ echo "Hello FastDFS!">index.html
bash-4.3$ fdfs_test /etc/fdfs/client.conf upload index.html
This is FastDFS client test program v5.11
...
[2017-11-28 14:05:25] DEBUG - base_path=/var/local/fdfs/storage, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0
tracker_query_storage_store_list_without_group:
        server 1. group_name=, ip_addr=192.168.56.110, port=23000
group_name=group1, ip_addr=192.168.56.110, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71.html
source ip address: 192.168.56.110
file timestamp=2017-11-28 14:05:25
file size=15
file crc32=3529255
example file url: http://192.168.56.110/group1/M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71.html

# 下载测试
bash-4.3$ fdfs_test /etc/fdfs/client.conf download group1 M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71.html
...
storage=192.168.56.110:23000
download file success, file size=15, file save to wKg4blodbSWAImI9AAAADwA12ic71.html

# 外部http访问测试
# 在主机或其他同局域网内机器上用浏览器访问上面的url。注意:若nginx的端口设置不为80,则需加上端口号
http://192.168.56.110:8888/group1/M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71.html
# 网页显示:Hello FastDFS!

# 删除测试
bash-4.3$ fdfs_test /etc/fdfs/client.conf delete group1 M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71.html
storage=192.168.56.110:23000
delete file success

# 退出容器终端
bash-4.3$ exit

管理容器

# 停止容器
$ docker stop <容器NAMES,也可以为容器ID的前几位>
或 $ docker-compose stop

# 更改compose或Dockerfile后重新生成并运行
$ docker-compose stop
$ docker-compose build
$ docker-compose up -d
或 $ docker-compose up -d --build #本条命令可代替上述三条命令

# 删除容器
$ docker rm <容器NAMES,也可以为容器ID的前几位>
或 $ docker-compose rm

查看日志

为避免每次需要查看日志都要执行 docker exec -it <fdfs ID> /usr/bin/tail -f <log_file> 命令,我将常见的查看日志命令封装到一个脚本中,每次只需要执行脚本就能查看不同服务的日志了。

新建日志监控脚本log.sh

新建log.sh,用来快速查看日志。内容如下:

#!/bin/bash
STORAGE=/var/local/fdfs/storage/logs/storaged.log
TRACKER=/var/local/fdfs/tracker/logs/trackerd.log
NGINX=/usr/local/nginx/logs/access.log

ID=`docker ps|grep fastdfs|awk '{print $1}'`
echo fastdfs.ID:$ID
echo 'Use param tracker|storage|nginx to see log of each service such as "./log.sh tracker". No param equals to "storage".'
CAT=$1
LOG=""
if [[ "${CAT}" = "tracker" ]];then
    LOG=${TRACKER}
elif [[ "${CAT}" = "nginx" ]]; then
    LOG=${NGINX}
else 
    LOG=${STORAGE}
fi

docker exec -it $ID /usr/bin/tail -f ${LOG}

给log.sh添加执行权限

$ chmod u+x log.sh

查看日志

$ ./log.sh tracker或storage或nginx

# 查看nginx日志,可以看到刚刚从外部http方式访问fastdfs文件的日志
$ ./log.sh nginx
192.168.56.100 - - [28/Nov/2017:14:10:33 +0000] "GET /group1/M00/00/00/wKg4blodbSWAImI9AAAADwA12ic71_big.html HTTP/1.1" 200 15 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36"

至此,fastDFS简易测试环境搭建完毕,开发搞起来!!完。

Ubuntu安装docker和docker-compose

未分类

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

我的理解:docker就是轻量化的虚拟机,不需要你在每个虚拟机里安装操作系统,做到最简单的新建、运行、修改、删除;而且容器内服务直接调用本机的操作系统API,性能更高;可以用来实现快速虚拟化、快速部署、持续集成等等,是运维同学的神器。

本人不是搞运维的,主要做开发工作。为什么学习docker呢?主要是用来快速部署开发、测试环境,比如你开发、测试时要用redis、zookeeper、activemq、分布式存储等等,在个人电脑上该怎么玩儿?搭建N台虚拟机?不光内存不够,硬盘也不够啊,而且现在内存价格很贵,加不起。我之前开了6台虚拟机做zookeeper+redis集群,本机再开个eclipse、浏览器,8G内存见底,卡得不要不要的,而且虚拟机文件就有10G以上,苦也。这时候就需要docker这种轻量化的容器大显身手了。docker化后,开一台1G内存虚拟机,开个zookeeper+redis集群+activeMQ+fastdfs轻轻松松,具体应用可以看下一篇博文《docker超快速搭建redis集群、zookeeper集群、activeMQ、fastdfs分布式存储教程》。

测试平台系统: Ubuntu 16.04。如果想在Windows上玩儿Docker,可以去官方下载docker-toolbox或者先安装一台Ubuntu虚拟机。(ps: 其实docker-toolbox底层也是用的VirtualBox软件新建一台Linux虚拟机来实现的,因为当前docker只支持Linux。建议自建虚拟机安装docker,因为toolbox默认安装的虚拟机系统是tiny-core-linux,毕竟是一个超小型的非常用的linux,而且用的ISO镜像系统,无法安装软件。下面开始正题。)

1. 修改Ubuntu APT源为国内源

备份并编辑source.list

$ sudo cp /etc/apt/source.list /etc/apt/source.list.bak
$ sudo nano /etc/apt/source.list

注释掉光盘源及官方源

注释掉以下面开头的源(注释即在行首加#)

# deb cdrom:[Ubuntu-Server 16.04.3 LTS _Xenial Xerus
# deb http://cn.archive.ubuntu.com/ubuntu/
# deb http://security.ubuntu.com/ubuntu 

添加国内源

在文件末尾加入如下内容。注意:下面的xenial仅对应Ubuntu 16.04,其他版本的Ubuntu请自行更改为对应的版本名称

# 163 注释掉源码源
deb http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse
# deb-src http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
# deb-src http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
# deb-src http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
# deb-src http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
# deb-src http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse

# aliyun 注释掉src源
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse 
# 源码
# deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
# deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse

2. 安装docker-ce

安装必要环境

$ sudo apt-get update
$ sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common

添加docker-ce仓库

两个安装源,二选一即可

使用阿里云docker-ce repository(版本基本同官方,速度快,推荐)

$ curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

使用官方docker-ce repository(版本最新,速度稍慢)

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

安装docker-ce

$ sudo apt-get -y update
$ sudo apt-get -y install docker-ce

# 添加当前用户到docker用户组
$ sudo usermod -aG docker `whoami`

# 检查docker
$ docker -v
Docker version 17.09.0-ce, build afdb6d4

修改镜像地址

$ sudo vim /etc/docker/daemon.json

# 输入如下内容:(此处采用docker中国官方镜像地址,若要采用aliyun等镜像仓库,请自行更改网址)
{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

# 重启docker
$ sudo /etc/init.d/docker restart

安装 docker-compose

$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.17.1, build 1719ceb

完。

在 docker 容器里使用 nginx-proxy 实现自动反向代理及负载均衡

一、Docker 简介

Docker是一个开放源代码软件项目,让应用程序布署在软件容器下的工作可以自动化进行,借此在Linux操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。Docker利用Linux核心中的资源分脱机制,例如cgroups,以及Linux核心名字空间(name space),来创建独立的软件容器(containers)。这可以在单一Linux实体下运作,避免引导一个虚拟机造成的额外负担.

1.1 docker 安装

Windows

Docker Windows 客户端需要开启 Hyper-V,安装过程会自动提示打开与否。

  • 从官网下载安装包双击安装。 https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe
  • 双击图标运行docker,初次打开可能需要几分钟时间。当右下角出现 docker 标志说明已经开始运行了!

未分类

  • 打开终端开始 docker 之旅

W
indows 下推荐使用 Docker for Windows 客户端,不要使用 docker toolbox。当从官方仓库拖取镜像时速度比较感人,建议使用国内镜像加速。

CentOS

1、安装依赖包

sudo yum install -y yum-utils 
  device-mapper-persistent-data 
  lvm2

2、用下面的命令设置 稳定版的 源地址

sudo yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo

3、安装 docker-ce

sudo yum install docker-ce

4、开始使用

sudo systemctl start docker
sudo docker run hello-world

Debian

系统需求:

  • 64 bit
  • Stretch (稳定版)
  • Jessie 8.0 (LTS)
  • Wheezy 7.7 (LTS)
  • Linux 内核大于 3.10

1、若安装有旧版本则先卸载

sudo apt-get remove docker docker-engine docker.io

2、安装依赖

sudo apt-get update
  • Jessie 或 Stretch 版:
sudo apt-get install 
    apt-transport-https 
    ca-certificates 
    curl 
    gnupg2 
    software-properties-common
  • Wheezy 版:
sudo apt-get install 
    apt-transport-https 
    ca-certificates 
    curl 
    python-software-properties

3、添加 Docker 官方 GPG key

curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -

设置 源地址

sudo add-apt-repository 
   "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") 
   $(lsb_release -cs) 
   stable"

4、安装 Docker-ce

sudo apt-get update
sudo apt-get install docker-ce

enjoy it:

sudo docker run hello-world

Debian 系统使用 apt-get 默认安装版本比较旧,使用 docker-compose 管理 Docker容器时会报错客户端和服务器版本不一致。建议按以上官方安装说明安装新版 Docker。

二、Nginx 介绍

Nginx 是一个 web 服务器。它类似于 Lighttpd,作为轻量级的 web server,可以替代重量级的 Apache/IIS。Nginx 专为性能优化而开发,是一个快速且能经受高负载考验的 web server。详情见 wiki 页面:

2.1 下载 Nginx 官方镜像

docker pull nginx

默认标签为最新版,若需要其它版本指定标签即可 请从docker hub获取帮助

三、nginx-proxy 介绍

nginx-proxy 启动一个容器来运行 nginx 和 docker-gen。 在主机上的容器启动和停止时 docker-gen 会生成 nginx 反向代理配置并且重新加载 nginx。

| Automated Nginx reverse proxy for docker containers

3.1 下载镜像

docker pull jwilder/nginx-proxy

如何使用

  • 启动 nginx-proxy 容器:
docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
  • 再启动一个需要被代理的容器,使用环境变量
    VIRTUAL_HOST=domain.com
docker run -e VIRTUAL_HOST=domain.com nginx

被代理的容器必须 expose 被代理的端口,可以通过在 Dockerfile 里面使用 EXPOSE 指令或者在 docker run 时使用 –expose 参数。

在 DNS 上设置转发 foo.bar.com 到 nginx-proxy 的主机上,之后请求就会被自动路由到设置了相同 VIRTUAL_HOST 环境变量的容器上。

四、使用 Letsencrypt 证书加密

letsencrypt-nginx-proxy-companion 是一个轻量级的代理容器,配合 nginx-proxy实现自动创建和自动更新 Let’s Encrypt 证书。

4.1 下载镜像

docker pull jrcs/letsencrypt-nginx-proxy-companion

4.2 用法

要将其与原始 nginx 代理容器一起使用,您必须从 nginx-proxy 容器声明3个可写卷:

  • /etc/nginx/certs 创建和更新 Let’s Encrypt 证书
  • /etc/nginx/vhost.d 更改虚拟主机配置 (Let’s Encrypt)
  • /usr/share/nginx/html 写入验证文件 (Let’s Encrypt)

启动 nginx-proxy

docker run -d -p 80:80 -p 443:443 
  --name nginx-proxy 
  -v /path/to/certs:/etc/nginx/certs:ro 
  -v /etc/nginx/vhost.d 
  -v /usr/share/nginx/html 
  -v /var/run/docker.sock:/tmp/docker.sock:ro 
  --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy 
  jwilder/nginx-proxy

–label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy 是必需的,否则 Let’s Encrypt 不知道使用哪个容器。

启动 LetsEncrypt

docker run -d 
  -v /path/to/certs:/etc/nginx/certs:rw 
  -v /var/run/docker.sock:/var/run/docker.sock:ro 
  --volumes-from nginx-proxy 
  jrcs/letsencrypt-nginx-proxy-companion

再启动需要被代理和加密的容器,加上环境变量 VIRTUAL_HOST=domain.com 。
docker run -e “VIRTUAL_HOST=foo.bar.com” …
要使其能自动创建和更新证书需设置环境变量 LETSENCRYPT_HOST LETSENCRYPT_EMAIL 即可。

4.3 Docker-compose

Compose 是定义和运行多容器Docker应用程序的工具。 使用Compose,您可以使用YAML文件来配置应用程序的服务。 然后,使用单个命令创建并启动配置中的所有服务。 要详细了解Compose的所有功能,请参阅功能列表。

Linux 安装

使用以下命令

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

赋可执行权限

sudo chmod +x /usr/local/bin/docker-compose

Windows 安装

以管理员身份运行 PowerShell

Invoke-WebRequest "https://github.com/docker/compose/releases/download/$dockerComposeVersion/docker-compose-Windows-x86_64.exe" -UseBasicParsing -OutFile $Env:ProgramFilesdockerdocker-compose.exe

使用 Compose

首先 创建一个 docker-compose.yml 的文件,在当前目录运行 docker-compose up 。

五、完整 docker-compose 文件

version: '3'
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    labels: 
        com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
    ports: 
      - 80:80
      - 443:443
    volumes:
      - /docker/nginx/certs:/etc/nginx/certs:ro
      - /docker/nginx/conf.d:/etc/nginx/conf.d
      - /docker/nginx/vhost.d:/etc/nginx/vhost.d
      - /docker/nginx/html/:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt
    volumes:
      - /docker/nginx/conf.d:/etc/nginx/conf.d
      - /docker/nginx/vhost.d:/etc/nginx/vhost.d
      - /docker/nginx/html/:/usr/share/nginx/html
      - /docker/nginx/certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
  someweb:  
    image: nginx
    container_name: someweb
    environment:
      - VIRTUAL_HOST=somedomain.com
      - LETSENCRYPT_HOST=somedomain.com
      - [email protected]
networks:
  default:
    external:
      name: web-proxy

构建基于nginx-php-yaf的docker镜像

  • 需求 : php7.1 + nginx
  • php扩展: yaf、redis、ldap、pdo、mbstring、 mcrypt

阅读完本文后,你能解决以下常见问题:

  • 如何写Dockerfile,并通过Dockerfile构建镜像。
  • 如何通过supervisord管理进程,并将进程日志通过docker logs {container}输出

实现步骤

我们的镜像基于centos:7系统, Dockerfile代码如下:

FROM centos:7
MAINTAINER [email protected]
RUN rpm -Uvh http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 
    && rpm -Uvh https://sp.repo.webtatic.com/yum/el7/webtatic-release.rpm 
    && yum update -y 
    && yum install -y wget 
        sed  
        gcc 
        gcc-c++ 
        gd 
        gd-devel 
        gmp-devel 
        epel-release 
        net-tools 
        ntpdate 
        ntp 
        openssh-clients  
        curl 
        crontabs 
        openssl 
        openssl-devel 
        nginx 
        squid 
        php71w 
        php71w-fpm 
        php71w-gd 
        php71w-soap 
        php71w-pdo_mysql 
        php71w-pear 
        php71w-devel   
        php71w-mbstring 
        php71w-mcrypt 
        php71w-ldap 
    && yum update -y ntp ntpdate kernel-headers 
    && yum clean all 
    && sed -i 's/http_access deny all/#http_access deny all/g' /etc/squid/squid.conf 
    && cd /tmp 
    && wget -O get-pip.py https://bootstrap.pypa.io/get-pip.py 
    && python get-pip.py 
    && pip install supervisor 
    && mkdir -p /etc/supervisor.d/*.conf 
    && pecl install mongodb 
    && pecl install yaf 
    && wget -c https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz 
        && tar zxvf 3.1.4.tar.gz 
        && cd phpredis-3.1.4 
        && phpize 
        && ./configure 
        && make 
        && make install 
        && cd .. 
        && rm -rf phpredis* 
    && rm 3.1.4.tar.gz  
    && echo "" >> /etc/php.ini 
    && echo "extension=redis.so" >> /etc/php.ini 
    && echo "extension=yaf.so" >> /etc/php.ini 
    && echo "extension=mongodb.so" >> /etc/php.ini 
    && echo "[yaf]" >> /etc/php.ini 
    && echo "yaf.use_namespace = 1" >> /etc/php.ini 
COPY ./supervisord.conf /etc/
COPY ./default.conf /etc/nginx/conf.d/
COPY ./nginx.conf /etc/supervisor.d/
COPY ./php.conf /etc/supervisor.d/
COPY ./ntpdate.conf /etc/supervisor.d/
COPY ./nginx.conf.def /etc/nginx/nginx.conf
COPY ./index.php /data/work/code/
EXPOSE 80 443 3128
WORKDIR /data/work/code
CMD ["supervisord","-c","/etc/supervisord.conf"]

问题

子进程stdout如何重定向到supervisord

Supervisor的监督进程supervisord可以捕获所管理子进程的stdout及stderr,并可以配置为写入日志文件
镜像初步构建成功时,运行容器,并通过 docker logs -f {container} 查看容器日志,总是提示错误, 如下:

2017-11-09 07:41:41,256 CRIT uncaptured python exception, closing channel <POutputDispatcher at 36463248 for <Subprocess at 35644984 with name nginx in state RUNNING> (stderr)> (<type 'exceptions.IOError'>:[Errno 29] Illegal seek [/usr/lib/python2.7/site-packages/supervisor/supervisord.py|runforever|227] [/usr/lib/python2.7/site-packages/supervisor/dispatchers.py|handle_read_event|232] [/usr/lib/python2.7/site-packages/supervisor/dispatchers.py|record_output|166] [/usr/lib/python2.7/site-packages/supervisor/dispatchers.py|_log|142] [/usr/lib/python2.7/site-packages/supervisor/loggers.py|info|275] [/usr/lib/python2.7/site-packages/supervisor/loggers.py|log|293] [/usr/lib/python2.7/site-packages/supervisor/loggers.py|emit|186] [/usr/lib/python2.7/site-packages/supervisor/loggers.py|doRollover|211])

经过了N多次查询,N多次尝试,终于找到原因,原来是在配置supervisord管理的子进程文件时,如果配置了stdout_logfile=/dev/stdout, 则必须一块配置stdout_logfile_maxbytes=0

查找的资料说明如下:

If anyone stumbles upon this error like me, be aware that the mentioned options apply to the [supervisord] section, within [program:x] sections, you need to use stdout_logfile_maxbytes = 0and/or stderr_logfile_maxbytes = 0 respectively.

using stdout_logfile=/dev/stdout along with stdout_logfile_maxbytes=0

对于需要将supervisord管理的子进程的日志输出到stdout的program, 配置如下:

[program:php7-fpm]
command=/usr/local/sbin/php-fpm
autostart = true
stderr_logfile = /dev/stdout
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0
stderr_logfile_maxbytes = 0

参见: http://veithen.github.io/2015/01/08/supervisord-redirecting-stdout.html

php-fpm 的错误日志如何定向到docker logs

看到 https://github.com/docker-library/php/issues/63 解释,将error_log及access.log的配置改成如下error_log=/proc/self/fd/2 和 access.log=/proc/self/fd2即可,一直不明白,/proc/self/fd/2和/dev/stdout /dev/stderr有什么联系,经过ls -al /dev 才明白:

lrwxrwxrwx  1 root root   15 Nov  9 07:44 stderr -> /proc/self/fd/2
lrwxrwxrwx  1 root root   15 Nov  9 07:44 stdin -> /proc/self/fd/0
lrwxrwxrwx  1 root root   15 Nov  9 07:44 stdout -> /proc/self/fd/1

对于我们的需求,是将错误及access信息都在docker logs 中显示,所以,我们配置php-fpm.conf如下:

[global]
error_log=/proc/self/fd/1

文件/etc/php-fpm.d/www.conf配置如下:

php_admin_value[error_log] = /proc/self/fd/1
slowlog = /proc/self/fd/1
catch_workers_output = yes
clear_env=no

docker安装mysql5.6

未分类

下载镜像

cjinle@debian:~$ sudo docker pull mysql:5.6
启动mysql
cjinle@debian:~$ sudo docker run --name mysql5.6 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6
80b766cca7ce0af73787a626153a92286cc7ab8c125e42c1c75e3e680ecefcf5

查看进程

cjinle@debian:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
80b766cca7ce        mysql:5.6           "docker-entrypoint..."   8 minutes ago       Up 8 minutes        0.0.0.0:3306->3306/tcp   mysql5.6
d4d5aa2e10f7        redis               "docker-entrypoint..."   3 hours ago         Up 3 hours          0.0.0.0:6379->6379/tcp   musing_wescoff

连接测试

用有mysql客户端的登录测试

[root@dev ~]# mysql -uroot -p123456 -h192.168.56.101
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 20
Server version: 5.6.38 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, 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.

mysql>

docker caddy 克隆私有仓库遇到的问题

问题描述

我使用的是 gogs 作为自己私有的 git server. 正常的将 .ssh 目录直接导入到了 docker 中. 然后启动 docker 报错如下

Warning: Permanently added the RSA host key for IP address
'xx.xx.xx.xx' to the list of known hosts.

想必经常玩vps的人对这个提示并不陌生.. 我们每次是有 ssh 尝试连接一台我们从没有连接过服务器都会出现, 但是在 docker 中如何避免这个提示?

解决

其实就是要跳过这个验证, 网上一搜基本就能找到. 将 StrictHostKeyChecking 直接配置到 .ssh/config 中 就可以了.

# 文件 .ssh/config
# 以 github.com 为例 自行替换成自己的 git server 地址
Host github.com
    StrictHostKeyChecking no

这样请求的时候就会跳过跳过验证直接 clone 代码了

微服务部署之Maven插件构建Docker镜像

微服务架构下,微服务在带来良好的设计和架构理念的同时,也带来了运维上的额外复杂性,尤其是在服务部署和服务监控上。单体应用是集中式的,就一个单体跑在一起,部署和管理的时候非常简单,而微服务是一个网状分布的,有很多服务需要维护和管理,对它进行部署和维护的时候则比较复杂。

下面从Dev的角度来看一下Ops的工作。从Dev提交代码,到完成集成测试的一系列步骤如下:

  • 首先是开发人员把程序代码更新后上传到Git,然后其他的事情都将由Jenkins自动完成。
  • 然后Git在接收到用户更新的代码后,会把消息和任务传递给Jenkins,然后Jenkins会自动构建一个任务,下载Maven相关的软件包。下载完成后,就开始利用Maven Build新的项目包,然后重建Maven容器,构建新的Image并Push到Docker私有库中。
  • 最后删除正在运行的Docker容器,再基于新的镜像重新把Docker容器启动,自动完成集成测试。

整个过程都是自动的,这样就简化了原本复杂的集成工作,一天可以集成一次,甚至是多次。

未分类

本文主要关注的第二步,作为Dev使用Maven插件构建Docker镜像。

一、过程步骤

1、环境

笔者的电脑系统是MacOS,在进行下面的步骤之前,先具备一下条件:

  • Docker Registry
  • Maven(3.5.0)
  • JDK(1.8.0_131)
  • Docker for Mac (17.09.0-ce-mac35)

Maven 和JDK 就不用过多多了,必须具有的。Docker Registry是私有的hub,mac上装好docker之后,配置一下Docker Registry的地址,配置如下:

  "debug" : true,
  "experimental" : false,
  "insecure-registries" : [
    "192.168.1.202"

未分类

2、pom文件

pom文件中需要引入相应的插件。docker-maven-plugin有三款:spotify、fabric8io和bibryam。其中第一款最为流行,资料也多,所以毫不犹豫选择第一款。
插件有两种使用方式,一种是在直接在pom配置中指定baseImage和entryPoint。另一种适合于复杂的构建,使用dockerfile,只需要在配置中指定dockerfile的位置。前一种比较简单,此处略过,主要讲下第二种的配置。

<plugin>
             <groupId>com.spotify</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <version>${maven.docker.version}</version>
             <!--插件绑定到phase-->
             <executions>
                 <execution>
                     <phase>install</phase>
                     <goals>
                         <goal>build</goal>
                     </goals>
                 </execution>
             </executions>
             <configuration>
             <!--配置变量,包括是否build、imageName、imageTag,非常灵活-->
                 <skipDocker>${docker.skip.build}</skipDocker>
                 <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
                 <!--最后镜像产生了两个tag,版本和和最新的-->
                 <imageTags>
                     <imageTag>${project.version}</imageTag>
                     <imageTag>latest</imageTag>
                 </imageTags>
                 <forceTags>true</forceTags>                 
                 <env>
                     <TZ>Asia/Shanghai</TZ>
                 </env>
                 <!--时区配置-->
                 <runs>
                     <run>ln -snf /usr/share/zoneinfo/$TZ /etc/localtime</run>
                     <run>echo $TZ > /etc/timezone</run>                      
                 </runs>
                 <dockerDirectory>${project.basedir}</dockerDirectory>
                 <resources>
                     <resource>
                         <targetPath>/</targetPath>
                         <directory>${project.build.directory}</directory>
                         <include>${project.build.finalName}.jar</include>
                     </resource>
                 </resources>
                 <!--push到私有的hub-->
                 <serverId>docker-registry</serverId>
             </configuration>
         </plugin>

${maven.docker.version}、${docker.skip.build}、${docker.image.prefix}都是可配置的变量。${project.basedir}、${project.build.directory}、${project.build.finalName}、${project.version}分别对应项目根目录、构建目录、打包后生成的结果名称、项目版本号。

上面的pom插件配置,指定了dockerfile的位置和镜像的命名规则。并将docker的build目标,绑定在install这个phase上。

3、dockerfile

FROM 192.168.1.202/library/basejava
VOLUME /tmp
ADD ./target/cloud-api-gateway-1.2.0.RELEASE.jar app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

dockerfile 写的很简单,将jar包ADD进去,提供ENTRYPOINT。

4、setting.xml

在pom插件中,还有一个serverId的配置。这个配置是必要的,对于需要将image上传到私有hub上,在如上配置之后,只需要加上-DpushImage即可实现。serverId是与maven的配置文件setting.xml相对应,setting.xml中增加的配置:

<server>
  <id>docker-registry</id>
  <username>用户名</username>
  <password>密码</password>
  <configuration>
    <email>邮箱</email>
  </configuration>
</server>

5、结果

未分类

上图是执行mvn clean install -DpushImage成功的结果。mvn首先是打包,将生产的文件拷贝到target下的docker目录,然后执行dockerfile中的步骤,将打成的镜像进行tag,最后上传到私有hub上。

未分类

上图是VMware Harbor中的截图,可以看到,我们已经成功将镜像上传,其tag有两个:1.2.0.RELEASE和latest。

本文属于工程实践类文章,比较简单。开头由背景介绍了Dev到继承测试的一系列步骤,本文主要关注的是第二步,作为Dev使用Maven插件构建Docker镜像。正文部分主要讲了实践的环境,然后讲了docker-maven-plugin插件的使用方式,重点介绍了使用dockerfile的方式,对于涉及到的配置进行了解释。

本文的源码地址:

GitHub:https://github.com/keets2012/snowflake-id-generator
码云: https://gitee.com/keets/snowflake-id-generator