Docker部署基于Nodejs的Web应用

Docker

docker是一个开源的应用容器引擎,可以为我们提供安全、可移植、可重复的自动化部署的方式。docker采用虚拟化的技术来虚拟化出应用程序的运行环境。此种方式具有以下优势:

  • 每个部署的应用程序都是一个容器,彼此隔离,互不影响;

  • 服务器只需要安装docker即可运行构建好的应用程序镜像,不会涉及复杂的服务器环境配置,因为配置都在特定的应用程序所在的镜像中去配置即可;

  • 简化了自动化部署和运维的繁琐流程,只需将构建好的镜像load到服务器的docker中即可运行我们的应用程序;

  • 可以充分利用服务器的系统资源,一台服务器上可以同时运行多个容器;

未分类

docker采用的是c/s架构,Client通过接口与Server进程通信实现容器的构建,运行和发布。docker比较重要的三个核心概念如下:

  • 镜像(images):一个只读的模板,可以理解为应用程序的运行环境,包含了程序运行所依赖的环境和基本配置,镜像可以按照层级(从基础镜像开始)来构建,每一层包含特定的环境。

  • 仓库(repository):一个用于存放镜像文件的仓库,如果你对git的仓库熟悉,应该很容易理解,对,就是那个。有私有仓库和公有仓库之分。

  • 容器(container):一个运行应用程序的虚拟容器,在我们运行镜像时产生。容器包含自己的文件系统+隔离的进程空间和包含其中的进程。

前言

sharplook是一款通过大数据分析来解决客户在监控系统中存在的数据采集难、解析难、处理难的IT运维产品。在给客户部署产品的过程中涉及到比较多的环境配置和组件安装以及复杂的依赖项,这些繁琐的流程降低了安装部署的效率和产品质量。基于此,我们开发了一款可以快速便捷的安装部署套件,提供一种漂亮的安装部署流程。产品采用 Nuxt + Koa 的基础架构进行开发,其中采用nuxt来提供SSR(服务端渲染)功能,Nuxt.js是基于Vue.js的通用架构,其中集成了以下组件:

  • Vue2 (https://github.com/vuejs/vue)

  • Vue-Router (https://github.com/vuejs/vue-router)

  • Vuex (https://github.com/vuejs/vuex)

  • Vue-Meta (https://github.com/declandewet/vue-meta)

另外,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 来处理代码的自动化构建工作(如打包、代码分层、压缩等等)。

我们项目使用Nuxt.js作为中间件来进行UI渲染,使用Koa启动我们自己的服务器,koa2 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。

关于如何快速搭建这样一个项目,小生之前在《vue-cli “从入门到放弃”》中介绍过vue-cli的使用,这个项目我们通过vue-cli工具,使用 nuxt-community/koa-template 模板,vue init nuxt-community/koa-template快速构建出来。具体的代码逻辑,在此不做赘述。

关于为什么选择docker来部署我们的node服务,前面已经介绍了,我们基于node的web应用涉及到的部署环境并不复杂,仅仅需要Node.js作为平台即可,由于依赖的包文件太多,而且比较大,业界还没有特别好用的开源node打包工具。做前端的同学都知道,webpack是一个功能强大的资源加载构建的打包工具,只需要将项目文件打包到一个dist文件下,打包后的文件体量小,解决了文件之间的依赖问题,提取出公共代码库,生产环境只需要部署dist文件夹即可。然而,Nodejs程序涉及到的依赖包,和资源却没法进行打包,node服务开启后仅仅是一个node进程而已。那么我们如何部署node程序呢?既然没法打包,只能将其全部文件进行部署(虽然可能存在别的问题,小生也在研究中,大神可以给点建议),于是小生就希望将其放入docker中去部署,免去生产环境node版本不一致等问题。下面小生就将如何用docker来部署Node项目的过程分享给诸位。

实战

以下将进入战备状态,请同志们准备好大脑和电脑,跟着我左手、右手一个慢动作。

环境准备

  • 安装docker,未安装的同学,请根据自己的开发环境采用不同的安装方式去安装,具体操作参考教程,不做赘述。

  • 安装成功后,可以通过docker -v查看版本号(尽量使用最新的稳定版本)。

项目准备

  • 在你的项目根目录下,添加Dockerfile文件,此文件用来配置我们自定义一个镜像所需要指定的依赖项、环境以及执行的命令等。内容格式如下:
# 指定我们的基础镜像是node,版本是v8.0.0
 FROM node:8.0.0
 # 指定制作我们的镜像的联系人信息(镜像创建者)
 MAINTAINER EOI

 # 将根目录下的文件都copy到container(运行此镜像的容器)文件系统的app文件夹下
 ADD . /app/
 # cd到app文件夹下
 WORKDIR /app

 # 安装项目依赖包
 RUN npm install
 RUN npm rebuild node-sass --force

 # 配置环境变量
 ENV HOST 0.0.0.0
 ENV PORT 8000

 # 容器对外暴露的端口号
 EXPOSE 8000

 # 容器启动时执行的命令,类似npm run start
 CMD ["npm", "start"]

关于Dockerfile文件中的关键字,解释如下:

  • FROM
    语法:FROM [:]
    解释:设置要制作的镜像基于哪个镜像,FROM指令必须是整个Dockerfile的第一个指令,如果指定的镜像不存在默认会自动从Docker Hub上下载。

  • MAINTAINER
    语法:MAINTAINER
    解释:MAINTAINER指令允许你给将要制作的镜像设置作者信息。

  • ADD
    语法:ADD
    解释:ADD指令用于从指定路径拷贝一个文件或目录到容器的指定路径中,是一个文件或目录的路径,也可以是一个url,路径是相对于该Dockerfile文件所在位置的相对路径,是目标容器的一个绝对路径。

  • WORKDIR
    语法:WORKDIR /path/to/workdir
    解释:WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,如果使用相对路径则为相对于WORKDIR上一次的值,例如WORKDIR /data,WORKDIR logs,RUN pwd最终输出的当前目录是/data/logs。

  • RUN
    语法:① RUN #将会调用/bin/sh -c
    ② RUN [“executable”, “param1”, “param2”] #将会调用exec执行,以避免有些时候shell方式执行时的传递参数问题,而且有些基础镜像可能不包含/bin/sh
    解释:RUN指令会在一个新的容器中执行任何命令,然后把执行后的改变提交到当前镜像,提交后的镜像会被用于Dockerfile中定义的下一步操作,RUN中定义的命令会按顺序执行并提交,这正是Docker廉价的提交和可以基于镜像的任何一个历史点创建容器的好处,就像版本控制工具一样。

  • ENV
    语法:ENV
    解释:ENV指令用于设置环境变量,在Dockerfile中这些设置的环境变量也会影响到RUN指令,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行docker run时添加–env =参数来修改。
    注意:最好不要定义那些可能和系统预定义的环境变量冲突的名字,否则可能会产生意想不到的结果。

  • EXPOSE
    语法:EXPOSE [ …]
    解释:EXPOSE指令用来告诉Docker这个容器在运行时会监听哪些端口,Docker在连接不同的容器(使用–link参数)时使用这些信息。

  • CMD
    语法: ① CMD [“executable”, “param1”, “param2”] #将会调用exec执行,首选方式
    ② CMD [“param1”, “param2”] #当使用ENTRYPOINT指令时,为该指令传递默认参数
    ③ CMD [ | ] #将会调用/bin/sh -c执行
    解释:CMD指令中指定的命令会在镜像运行时执行,在Dockerfile中只能存在一个,如果使用了多个CMD指令,则只有最后一个CMD指令有效。当出现ENTRYPOINT指令时,CMD中定义的内容会作为ENTRYPOINT指令的默认参数,也就是说可以使用CMD指令给ENTRYPOINT传递参数。
    注意:RUN和CMD都是执行命令,他们的差异在于RUN中定义的命令会在执行docker build命令创建镜像时执行,而CMD中定义的命令会在执行docker run命令运行镜像时执行,另外使用第一种语法也就是调用exec执行时,命令必须为绝对路径。

其中还有其他的一些关键字:USER、ENTRYPOINT、VOLUME、ONBUILD等,如果你有兴趣可以自行研究。

在项目根目录下添加.dockerignore文件,此文件的作用类似.gitignore文件,可以忽略掉添加进镜像中的文件,写法、格式和.gitignore一样,一行代表一个忽略。本项目添加的忽略如下:

  .DS_Store
  npm-debug.log*
  selenium-debug.log
  .nuxt/
  /package-lock.json
  *.tar
  *.md

  # Editor directories and files
  .idea
  *.suo
  *.ntvs*
  *.njsproj
  *.sln

构建镜像

  • 查看目前本地docker的镜像
> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

cd 到项目根目录下,执行以下命令

> docker build -t deploy:1.0 .

    Sending build context to Docker daemon  1.436GB
  .... 此处省略1000个字符。
  Successfully built d8f0875e967b
  Successfully tagged deploy:1.0

deploy是镜像名,1.0是镜像的版本号,到此你已经成功构建了一个新的镜像,你可以通过docker images,查看你的镜像。

> docker images
 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 deploy              1.0                 d8f0875e967b        3 minutes ago        2.11GB

启动镜像,测试是否成功。

> docker run -d -p 9000:8000 deploy:1.0
8aec5ee037bb253901d2c2e02c7be546580546c493576139f3789fb660f3401d

> docker ps -a
CONTAINER ID    IMAGE        COMMAND          CREATED           STATUS         PORTS                  NAMES
8aec5ee037bb    deploy:1.0   "npm start"     57 seconds ago    Up 56 seconds  0.0.0.0:9000->8000/tcp amazing_bassi

docker run -d -p 9000:8000 deploy:1.0中-d表示后台运行,-p 9000:8000表示指定本地的9000端口隐射到容器内的8000端口。 deploy:1.0为我们要运行的镜像。通过docker ps -a查看docker的进程(容器的运行本身就是一种特殊的进程)运行情况,发现我们的容器已经在运行。本地可以访问localhost:9000。

通过docker logs可以查看我们容器内应用进程的运行日志。docker logs

> docker logs 8aec5ee037bb
  npm info it worked if it ends with ok
  npm info using [email protected]
  npm info using [email protected]
  npm info lifecycle [email protected]~prestart: [email protected]
  npm info lifecycle [email protected]~start: [email protected]

  > [email protected] start /app
  > node ./server/index.js

  Server listening on 0.0.0.0:8000
   DONE  Compiled successfully in 9310ms06:55:56

  > Open http://0.0.0.0:8000
docker stop <CONTAINER ID>可以停止容器运行

 docker start <CONTAINER ID>可以启动容器运行

 docker restart <CONTAINER ID>可以重启容器

 docker rm <CONTAINER ID> -f可以强制删除在运行的容器

上传镜像(这里用上传到公共仓库来演示)

没注册DockerHub的同学,请注册DockerHub

登录docker

> docker login
Username: XXX
Password: XXX
Login Succeeded

docker tag /上传之前必须给镜像打上tag,namespace可以指定为你的docker Id

> docker tag deploy:1.0 lzqs/deploy:1.0

docker push /将镜像上传至docker的公共仓库

> docker push lzqs/deploy:1.0

上传成功后,docker logout 退出,登录 https://hub.docker.com/ 查看上传的镜像。

下载镜像

通过docker pull /下载我们的镜像。

> docker pull lzqs/deploy:1.0

生产部署

前面说了,我们可以将上传到仓库的镜像下载下来部署,但是如果镜像比较大或者部署环境压根无法联网,你是不是要跪了。所以我们采取另一种方法,将开发好的镜像直接打包保存到安装盘里面,到客户生产环境再将镜像包上传并加载到服务器的docker中即可。

在开发环境打包,docker save / .tar

> docker save lzqs/deploy:1.0 > deploy.tar

这里ls会发现目录下生成了deploy.tar的文件。部署时将此文件copy到生产环境服务器上。

确保生产服务器上已经安装了docker,若没装,请参考相关文档,若不装,对不起小生也无力了,然后在服务器上加载上传的镜像包deploy.tar。

> docker load < deploy.tar

  007ab444b234: Loading layer [==================================================>] 129.3 MB/129.3 MB
  4902b007e6a7: Loading layer [==================================================>] 45.45 MB/45.45 MB
  bb07d0c1008d: Loading layer [==================================================>] 126.8 MB/126.8 MB
  ecf5c2e2468e: Loading layer [==================================================>] 326.6 MB/326.6 MB
  7b3b4fef39c1: Loading layer [==================================================>] 352.3 kB/352.3 kB
  677f02386f07: Loading layer [==================================================>] 137.2 kB/137.2 kB
  7333bb4665b8: Loading layer [==================================================>] 55.66 MB/55.66 MB
  e292e64ffb88: Loading layer [==================================================>] 3.757 MB/3.757 MB
  ee76d0e6f6d9: Loading layer [==================================================>] 1.436 GB/1.436 GB
  33dca533c6e5: Loading layer [==================================================>] 331.8 kB/331.8 kB
  24630015679d: Loading layer [==================================================>] 35.18 MB/35.18 MB
  Loaded image: lzqs/deploy:1.0

加载成功后,docker images即可看到加载的镜像

> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
lzqs/deploy         1.0                 d8f0875e967b        About an hour ago   2.115 GB

运行lzqs/deploy镜像,成功后,在外部访问服务器的9000端口, <服务器的IP>:9000

> docker run -d -p 9000:8000 lzqs/deploy
1d0db9a5d0c8826171e501b0e86afd444fca8144b1105e63dae8d621bdda7a77

> docker ps -a
CONTAINER ID  IMAGE           COMMAND      CREATED              STATUS             PORTS                    NAMES
1d0db9a5d0c8  lzqs/deploy:1.0 "npm start"  About a minute ago   Up About a minute  0.0.0.0:9000->8000/tcp goofy_curran

docker exec -it /bin/bash 可以进入容器中执行,方便我们查看内部文件和调试

> docker exec -it 1d0db9a5d0c8 /bin/bash

root@1d0db9a5d0c8:/app#

战功,访问部署的docker应用,<服务器的IP>:9000,效果如下图:

未分类

使用bash脚本自定义创建postgres docker容器

1. 查看镜像库中postgres镜像

Docker search postgres 

2. 下载镜像

docker pull postgres

3.查看镜像

docker images

4.配置sh脚本

mkdir  postgres  (创建文件夹)

cd postgres 

mkdir data (创建数据文件夹)

touch postgres.sh (常见sh脚本文件)

vi postgres.sh(编辑脚本)

粘贴以下代码到postgres.sh文件中

#!/bin/sh  

NAME=hy-postgres  
PORT=5432  
CURDIR=`pwd`  
PASSWORD=123456  
case "$1" in  
    create)  
        port_map="-p 172.17.0.1:5432:5432"  
        volumn_map="-v $PWD/data:/data"  
        env_map="-e POSTGRES_PASSWORD=$PASSWORD"   
        docker run --name $NAME -d $env_map $port_map $volumn_map postgres:9.4.3  
        ;;  
    delete)  
        docker rm $NAME  
        ;;  
    start)  
        docker start $NAME  
        ;;        
    stop)  
        docker stop $NAME  
        ;;  
    status)  
        docker ps -a | grep $NAME  
        ;;  
    restart)  
        docker restart $NAME  
        ;;    
    bash)  
        docker exec -it $NAME bash  
        ;;  
    exec)  
        shift  
        docker exec -it $NAME $*  
        ;;  
    *)  
        echo "Usage: $0 {start|stop|status|bash|exec|restart}"  
        exit 1  
    ;;  
esac  

5. 使用脚本启动镜像

./postgres.sh create(创建镜像)

./postgres.sh start(启动镜像)

利用docker-compose安装lnmp(Nginx mariadb php7.0 )

对于Docker来说,最大的便利就是能快速的搭建起一个个的容器,容器之间可以通过网络和文件来进行通信。

之前我已经将自己的博客使用docker搭建起来了,这里简单记录一下docker-compose文件内容。

我的博客的架构为lnmp,依赖的容器有:

  • Nginx(Port:80)

  • mariadb(Port:3306)

  • wordpress+php7.0-fpm(Port:9000)

  • phpmyadmin(Port:8009)

docker-compose.yml文件内容如下

nginx:
    image: nginx:latest
    ports:
        - '80:80'
    volumes:
        - ./nginx:/etc/nginx/conf.d
        - ./logs/nginx:/var/log/nginx
        - ./jialeens:/var/www/html
    links:
        - wordpress
    restart: always

mysql:
    image: mariadb
    ports:
        - '3306:3306'
    volumes:
        - ./db-data:/var/lib/mysql
    environment:
        - MYSQL_ROOT_PASSWORD=******
    restart: always

wordpress:
    image: wordpress:4.8.0-php7.0-fpm
    ports:
        - '9000:9000'
    volumes:
        - ./jialeens:/var/www/html
    environment:
        - WORDPRESS_DB_NAME=***
        - WORDPRESS_TABLE_PREFIX=wp_
        - WORDPRESS_DB_HOST=mysql
        - WORDPRESS_DB_PASSWORD=*****
    links:
        - mysql
    restart: always
phpmyadmin:
  image: phpmyadmin/phpmyadmin
  links:
    - mysql
  environment:
    PMA_HOST: mysql
    PMA_PORT: 3306
  ports:
    - '8009:80'

Nginx配置文件:

jialeens.com.conf

server {
    listen 80;
    server_name jialeens.com www.jialeens.com;

    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;
    client_max_body_size 100m;
    root /var/www/html;
    index index.php;

    access_log /var/log/nginx/jialeens-access-http.log;
    error_log /var/log/nginx/jialeens-error-http.log;

    if ($host = 'jialeens.com') {
        return 301 http://www.jialeens.com$request_uri;
    }
    location ~* ^.+.(js|ico|gif|jpg|jpeg|png|html|htm)$ {
       log_not_found off;
       access_log off;
       expires 7d;
    }
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PHP_VALUE "upload_max_filesize=128M n post_max_size=128M";
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

因为流量不大,所以没做fastcgi的缓存,以后有空再弄吧。

Docker OOM事件介绍

OOM(Out Of Memory)内存不足,通常是由于某些不稳定的进程占用过多的内存造成,在Docker中称为OOM事件,当容器使用的内存过多时就会发生OOM事件,这个事件是由Linux内核的内存管理机制发起,并将是使用占用内存过多的容器Kill掉,保证系统的可持续运行。Linux内核为了保证系统的稳定性而将内存划分为两大部分用户空间与内核空间

未分类

用户空间是提供给用户进程所使用的内存空间。内核空间是仅提供给内核运行的空间。用户的进程是无法访问内核空间,而内核是可以访问用户空间与内核空间。在Linux内存管理机制中还存在一个定时任务,检查计算机的内存是否足够使用,分别收集以下几个指标

  • Total page cache as page cache is easily reclaimed
  • Total free pages because they are already available
  • Total free swap pages as userspace pages may be paged out
  • Total pages managed by swapper_space although this double-counts the free swap – pages. This is balanced by the fact that slots are sometimes reserved but not used
  • Total pages used by the dentry cache as they are easily reclaimed
  • Total pages used by the inode cache as they are easily reclaimed

如果内核发现内存不足够使用时开始发起OOM的状态检查,接着调用out-of-memory函数查找使用内存最多的进程并kill掉 oom

未分类

在Docker的容器中默认是没有限制资源使用的,也就是说容器获得到CPU/内存与宿主机是一样的,为了避免OOM事件,可以给Docker的容器作一些调整

  • 通过性能测试后才放到生产环境的容器中

  • 确保主机上有足够的资源分配

  • 使用SWAP(交换空间)

  • 将容器转换到有足够内存的Docker Swarm的服务中

注意:Docker不建议手动调整–oom-score-adj与–oom-disable-kill选项来避免OOM。

升级docker的方法

1、下载docker最近稳定版

curl -sSL -O https://get.docker.com/builds/Linux/x86_64/docker-1.9.1

2、停止docker服务并备份文件

service docker stop
mv /usr/bin/docker /usr/bin/docker_bak

3、升级docker

mv docker-1.9.1 /usr/bin/docker
chmod +x /usr/bin/docker
service docker start

4、查看最新版本

docker -v

Docker默认网络、自定义网络和overlay网络介绍

前言

前面总结了Docker基础以及Docker存储相关知识,今天来总结一下Docker单主机网络的相关知识。毋庸置疑,网络绝对是任何系统的核心,他在Docker中也占有重要的作用。同样本文基于CloudMan的系列教程。感谢ColudMan无私分享。

一、Docker默认网络

在新安装docker的主机上执行

docker network ls

便能看到docker默认安装的所有网络,分别是none网络、host网络和bridge网络。

1.1 none 网络

none网络就是什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器run时,可以通过添加–network=none参数来指定该容器使用none网络。那么这样一个只有lo的网络有什么用呢?此处CloudMan指出:

  • none网络应用与隔离场景,一些对安全性要求高并且不需要联网的应用可以使用none网络。

  • 比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。

我可以理解none网络肯定是用于隔离的,然而我好奇的是生成的随机密码如何发送到外部呢?如何被外部调用呢?这是我没有想明白的问题。有知道的希望不吝赐教!谢谢!

1.2 host 网络

连接到host网络的容器共享Docker宿主机的网络栈,即容器的网络配置与host宿主机完全一样。可以通过添加–network=host参数来指定该容器使用host网络。

在容器中可以看到host的所有网卡,并且连hostname也是host的。host网络的使用场景又是什么呢?

直接使用Docker host的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。

Docker host的另一个用途是让容器可以直接配置 host 网路。比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置。

相当于该容器拥有了host主机的网络,那么其ip等配置也相同,相当于主机中套了一个与外部一模一样的容器,可以直接通过host的ip地址来访问该容器。

1.3 bridge 网络

在不指定–network参数或者–network=bridge的情况下创建的容器其网络类型都是bridge。

Docker在安装时会在宿主机上创建名为docker0的网桥,所谓网桥相当于一个虚拟交换机,如果使用上述两种方式run的容器都会挂到docker0上。

容器和docker0之间通过veth进行连接,veth相当于一根虚拟网线,连接容器和虚拟交换机,这样就使得docker0与容器连通了。

二、自定义容器网络

理论上有了上述三种网络已经足够满足普通用户的需求,但是有时候可能用户需要指定自己的网络,以此来适应某些配置,如ip地址规划等等。

2.1 创建自定义网络

Docker提供三种user-defined网络驱动:bridge,overlay和macvlan。overlay和macvlan用于创建跨主机的网络,会在下一篇文章介绍。所以本文介绍创建bridge自定义网络。命令如下:

docker network create -d bridge --subnet 172.10.0.0/24 --gateway 172.10.0.1 my_net

-d bridge表示自定义网络的驱动为bridge,–subnet 172.10.0.0/24 –gateway 172.10.0.1分别指定网段和网关。

这样就创建好了一个自动一网络,可以通过以下命令查看此网络的信息:

docker network inspect my_net

会得到此网络的配置信息,my_net是刚刚创建的网络名称,如果为bridge就是查看docker创建的默认bridge网络信息。

每创建一个自定义网络便会在宿主机中创建一个网桥(docker0是创建的默认网桥,其实原理是一致的,而且也是对等的。)。名字为br-<网络短ID>,可以通过brctl show命令查看全部网桥信息。

docker的自定义网络与OpenStack中的网络信息倒是基本一致。所以一通百通,只要docker的明白了,所有虚拟化甚至实体的网络也就基本都搞清楚了。

2.2 使用自定义网络

通过以下命令为容器指定自定义网络:

docker run -it --network my_net --ip 172.10.0.3 busybox

其实这与使用docker默认网络是一致的,都是添加–network参数参数,此处也添加了–ip参数来指定容器的ip地址。

三、不同容器之间的连通性

同一个网络(默认网络或者自定义网络)下的容器之间是能ping通的,但是不同网络之间的容器由于网络独立性的要求是无法ping通的。原因是iptables-save DROP掉了docker之间的网络,大概如下:

-A DOCKER-ISOLATION -i docker0 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o docker0 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o br-62f17c363f02 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-62f17c363f02 -j DROP

那么如何让不同网络之间的docker通信呢?接下来介绍容器间通信的三种方式。

3.1 IP 通信

IP通信就是直接用IP地址来进行通信,根据上面的分析需要保证两个容器处于同一个网络,那么如果不在同一个网络如何处理呢?

如果是实体机我们很容易理解,只需要为其中一台服务器添加一块网卡连接到另一个网络就可以了。容器同理,只需要为其中一个容器添加另外一个容器的网络就可以了。使用如下命令:

docker network connect my_net httpd

connect命令能够为httpd容器再添加一个my_net网络(假设httpd原来只有默认的bridge网络)。这样上面创建的busybox容器就能与此次connect的httpd容器进行通信。

3.2 Docker DNS Server

通过 IP 访问容器虽然满足了通信的需求,但还是不够灵活。因为我们在部署应用之前可能无法确定IP,部署之后再指定要访问的IP会比较麻烦。对于这个问题,可以通过docker自带的DNS服务解决。

从Docker 1.10 版本开始,docker daemon 实现了一个内嵌的DNS server,使容器可以直接通过“容器名”通信。

方法很简单,只要在启动时用–name为容器命名就可以了。

下面的命令启动两个容器bbox1和bbox2:

docker run -it --network=my_net --name=bbox1 busybox
docker run -it --network=my_net --name=bbox2 busybox

然后,bbox2就可以直接ping到bbox1了,但是使用docker DNS有个限制,只能在user-defined网络中使用。默认的bridge网络是无法使用的。

3.3 joined 容器

joined 容器是另一种实现容器间通信的方式。joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信。host网络使得容器与宿主机共用同一个网络,而jointed是使得两个容器共用同一个网络。

请看下面的例子:

先创建一个httpd容器,名字为web1。

docker run -d -it --name=web1 httpd

然后创建busybox容器并通过–network=container:web1指定jointed容器为web1:

docker run -it --network=container:web1 busybox

这样busybox和web1的网卡mac地址与IP完全一样,它们共享了相同的网络栈。busybox 可以直接用127.0.0.1访问web1的http服务。

其实也很容易理解,之前的–network参数指定了默认网络或者自定义网络,而此处是指定了一个容器,那么当然意思就是使用这个容器的网络。这也有点类似上一篇文章讲到的共享存储。

joined 容器非常适合以下场景:

  • 不同容器中的程序希望通过loopback高效快速地通信,比如web server与app server。

  • 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。

其实就是应用于即需要独立而又需要两个容器网络高度一致的场景。

3.4 容器与外部网络的连通性

  • 容器访问外部网络

容器默认是能访问外部网络的。通过NAT,docker实现了容器对外网(此处外网不一定是互联网)的访问。

  • 外部网络访问容器

通过端口映射的方式实现外部网络访问容器,即通过-p参数实现将容器的端口映射到外部端口。

四、总结

本文简单总结了单主机情况下容器网络的相关知识和使用方案,重点是使用方案,如果需要理解原理可以直接查阅CloudMan的相关教程。下一章总结跨主机容器网络。

Docker环境的CI/CD持续集成持续交付

Docker的最大好处之一是能够带来无缝的CI/CD流程;容器是Docker镜像的运行着的只读实例,更新容器仅仅需要更新镜像文件,然后从这个更新过的镜像重新部署容器。还有免费的工具可以监控镜像repository,并且在检测到镜像有更新的时候立即重新部署一个容器。但是,运行容器,创建以及更新镜像,这仍然是手动的。

将CI/CD的概念带入下一个级别是需要创建开发流水线,将软件交付流程里的所有步骤自动化。典型的单服务流水线包括如下基本步骤:

  • 构建 – 初始化构建过程,将源码转变为编译好的artifact,并且打包到Docker镜像里

  • 测试 – 使用支持你的框架的任意测试工具在Docker容器里运行单元测试。

  • 推送 – 将测试过的Docker镜像推送到Docker registry服务,比如Docker Hub上。

  • 部署 -从registry服务下载docker镜像到相应的staging/production Docker环境里。

  • 运行 – 从一个或者多个镜像里初始化容器或者服务

CI/CD的下一个逻辑上的扩展是通过webhook将开发代码repository完全集成到开发流水线上;这样,当事件,比如代码repository里发生代码提交或者merge时,就会自动执行构建-部署流水线流程。有了这样的集成,无论开发人员什么时候提交代码到repository里,几秒钟后,带有开发人员变更的Docker容器就会生成,之后就可以用于实时的集成测试了。

Emerging Technology Partners调研并且验证了当前市场上能够提供最全面的容器为中心的CI/CD特性的解决方案。我们的方案可以提供集成测试所需的完整的环境。每个build里UI测试或者性能测试都是测试的一部分,让大家可以完整测试任意提交或者PR。使用我们的解决方案,开发人员和测试任意可以快速发现regression,在staging前修复这些问题,加速了开发周期,并且节省了时间和精力。

如果你负责内部的应用程序开发团队,并且在测试或者发布频率上挣扎,那么可以给ETP电话,我们可以向你展示容器中心的CI/CD可以如何集成到你的开发流程里。

使用awk求指定列的最大值最小值

需求:一文件内容如下,求第一列的最大值和最小值

1 1220
2 1221
3 1222
3 1223
4 1224
5 1225
12 1226
12 1227
12 1228
12 1229

12 1230

命令如下

求最小值:

sed '/^$/d' test.txt|awk 'NR==1{min=$1;next}{min=min<$1?min:$1}END{print min}' 

求最大值:

sed '/^$/d' test.txt|awk 'NR==1{max=$1;next}{max=max>$1?max:$1}END{print max}' 

利用sed删除空行,利用awk筛选出最大值和最小值。

AWK 两个文件字段合并处理实例

一、概念解析

1、awk命令概念

$0 表示一个文本中的一行记录

$1...N 表示一行中的第 1...N 字段

FNR     The input record number in the current input file.  #已读入当前文件的记录数

NR      The total number of input records seen so far.      #已读入的总记录数

next    Stop processing the current input record. The next input record is

       read and processing starts over with the first pattern in the AWK

       program. If the end of the input data is reached, the END block(s),

       if any, are executed.

2、处理两个文本文件

执行处理顺序:

首先,对file1执行“NR==FNR{…}”第一个循环,建立哈希数组;第二步,执行“NR>FNR{…}”第二个循环,打印输出命令结果。

  • 一种是
awk 'NR==FNR{...}NR>FNR{...}' file1 file2 或 awk 'NR==FNR{...}NR!=FNR{...}' file1 file2
  • 另一种是
awk 'NR==FNR{...;next}{...}' file1 file2

二、处理两个文件实例

1、处理实例一

file1:

文件内容:

sina.com 52.5

sohu.com 42.5

baidu.com 35

file 2:

文件内容:

www.news.sina.com sina.com 80

www.over.sohu.com baidu.com 20

www.fa.baidu.com sohu.com 50

www.open.sina.com sina.com 60

www.sport.sohu.com sohu.com 70

www.xxx.sohu.com sohu.com 30

www.abc.sina.com sina.com 10

www.fa.baidu.com baidu.com 50

www.open.sina.com sina.com 60

www.over.sohu.com sohu.com 20


awk 'NR==FNR{a[$1]=$2;next}{print $0,a[$2]}' f1 f2

命令结果:

www.news.sina.com sina.com 80 52.5

www.over.sohu.com baidu.com 20 35

www.fa.baidu.com sohu.com 50 42.5

www.open.sina.com sina.com 60 52.5

www.sport.sohu.com sohu.com 70 42.5

www.xxx.sohu.com sohu.com 30 42.5

www.abc.sina.com sina.com 10 52.5

www.fa.baidu.com baidu.com 50 35

www.open.sina.com sina.com 60 52.5

www.over.sohu.com sohu.com 20 42.5

2、处理实例二

需要处理的同名字段可以在两个文件中行号不同的行,无需行号排序相对应,

命令结果的行顺序依据第二个文件中同名字段顺序输出。

f1

文件内容:

10020036 beijing

10050259 lanzhou

10045682 hefei

20130495 guangzhou

20981345 shenzhen

20984748 chengdu

20891376 changsha

f2

文件内容:

guangzhou 4.5

hefei 2.6

beijing 1.3

shenzhen 8.5

changsha 0.8

chengdu 2.0

lanzhou 2.4


awk 'NR==FNR{a[$2]=$1}NR>FNR{print a[$1],$0}' f1 f2

命令输出:

20130495 guangzhou 4.5

10045682 hefei 2.6

10020036 beijing 1.3

20981345 shenzhen 8.5

20891376 changsha 0.8

20984748 chengdu 2.0

10050259 lanzhou 2.4

awk如何指定多分隔符

awk的-F参数可以指定新的分隔符,有些时候可能需求指定多个分隔符,比如下面的内容

[root@N1 ~]# netstat -an | grep ESTAB 
udp        0      0 192.168.1.120:35570     212.47.249.141:123      ESTABLISHED
udp        0      0 192.168.1.120:55589     108.59.2.24:123         ESTABLISHED

如果需要取出源IP的话,一般大家会这样做,即做两次awk操作

[root@N1 ~]# netstat -an | grep ESTAB | awk  '{print $5}' | awk -F: '{print $1}'
108.59.2.24
212.47.249.141

其实呢,通过在awk中指定两个分隔符(空格和:),即可一次性的提取出源IP地址,在awk中支持多个分隔符的写法如下:

[root@N1 ~]# netstat -an | grep ESTAB | awk -F '[ :]+' '{print $6}'
108.59.2.24
212.47.249.141
#多一个加号表明将连续出现的分隔符当做一个来处理

注意:

在awk中,当分隔符指定为空字符串时,awk会将多个连续的空白看做一个单一的分隔符。此外,awk还会忽略开头和结尾处的空白。