Docker 是 Docker 公司的开源项目,使用 Google 公司推出的 Go 语言开发的,并于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。
但是,许多人并不清楚 Docker 到底是什么,要解决什么问题,好处又在哪里?本文就来详细解释,帮助大家理解它,还带有简单易懂的实例,教你如何将它用于日常开发。
Docker 解决的痛点
我们知道,一个程序要跑起来,需要这么几部分:代码 + 运行环境 + 配置 + 依赖的服务。代码当然就是同一份代码,不同的环境都一样,通常不会有问题,Docker image中包含了运行环境+配置,这对部署相当友好。如果你没有做过这种系统(其实大部分人都没有做过啦),但是你肯定装过软件,装一些复杂的软件的时候有没有因为版本依赖或者编译参数等让你抓狂?
一、 环境配置一致性
用户必须保证两件事:操作系统的设置,各种库和组件的安装。只有它们都正确,软件才能运行。例如:我们开发SSO项目本地使用PHP7.1 + yaf2.0开发,如果线上跟本地不一致,可能会导致开发完成的SSO再线上无法正常运行。
另外,还包括数据的不一致性导致的问题无法复现、测试和开发环境不一致导致的沟通成本、自动化测试环境跟开发环境不一致导致的调试成本等。
二、快速部署
开发出的软件如何快速部署到线上是运维面临的一个大问题。尤其是要求快速扩容快速部署,这种情况下运维需要耗费很大精力搭建运行环境,编译软件等,而docker可编译成镜像,能够统一分发到不通服务器并启动,保证了环境一致,代码一致。
三、资源占用
简单的说Docker是一个构建在LXC之上的,基于进程容器(Processcontainer)的轻量级VM解决方案拿现实世界中货物的运输作类比,为了解决各种型号规格尺寸的货物在各种运输工具上进行运输的问题,我们发明了集装箱。
相对于传统虚拟化技术,Docker更轻量级,能够快速启动,从创建容器到启动容器可在1s中完成,而传统虚拟机可能要十几秒到几分钟。
传统虚拟化:
Docker:
总结如下:
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB | 一般为GB |
性能 | 接近原生 | 弱 |
系统支持量 | 单机支持上千个容器 | 一般支持几十个 |
总之,容器有点像虚拟化的进程,能够提供虚拟机的环境,但是成本开销小得多。
Docker中的概念解析
Dockerfile 文件
Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。它们简化了从头到尾的流程并极大的简化了部署工作。Dockerfile从FROM命令开始,紧接着跟随者各种方法,命令和参数。其产出为一个新的可以用于创建容器的镜像。
可以将Dockerfile理解为linux上的脚步,而这个脚本是用来构建Docker镜像的,由Docker主程序来解析并执行。
镜像(image):
镜像是Docker容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。可以将镜像理解为 XP 时代的 ghost 系统镜像,只有有了镜像才能安装成一个可用的电脑。 Docker的镜像也是一样,只有有了镜像,才能够正常创建容器,并运行。
镜像的构建是通过Dockerfile文件中的命令编译而成,Dockerfile文件每条命令都会将执行结果叠加到镜像中,类似于翻书,因此,镜像的结构如下:
同一个程序的多个版本镜像,可以用标签来识别,例如Nginx的镜像可以表示为:nginx:1.11.0
、nginx:latest
等,默认最后一个版本的镜像标签为latest
。当拉取镜像是,如果不加标签,默认拉去的也是latest
标签的镜像。
仓库(Repository)
就是镜像仓库,可以类比为我们平常使用的github,来储存我们的代码。而镜像也是需要储存到仓库中的。
如果我们部署,我肯定就要用 dockerhub, 我们把镜像托管到 docker hub 上(当然我们也可以假设,或者是用别人假设的hub)国内有很多三方 dockerhub 服务器, 有阿里云,网易蜂巢,有容云,daocloud 等等等等。
众所周知的原因,官方仓库国内是无法访问的,因此我们可以用阿里云或腾讯云托管我们的镜像,也可以通过官方提供的工具搭建私有仓库来存放公司私有镜像。
在通过Dockerfile文件编译镜像时,有些依赖也需要访问墙外的资源,我们也可以通过阿里云或腾讯云的服务,在国外编译基础镜像之后,在自己的主机上使用镜像。
容器(Container )
容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
通过执行 docker create
命令可以基于一个镜像来创建出容器,例如:
docker create --name mynginx nginx:latest
从基础镜像nginx
创建出一个名为mynginx
的容器。容器是可以启动、运行和停止的。
端口映射
创建了容器,我们就可以启动这个容器。但是如何将容器中运行的程序例如Nginx监听的80或443端口让宿主机或第三方客户端访问呢?
这就需要端口映射,将容器中程序监听的端口映射为宿主机端口,来接受来自外部的网络请求。
docker run -d -p 80:80 --name mynginx nginx:latest
如上所示,我们将创建一个基于Nginx的容器,并将80端口映射到宿主机的80端口。
目录映射
正常情况下,运行中产生的文件会保留 在容器内部,当容器重启时,文件并不会消失,但是如果删除了容器,会导致文件丢失。有些类型的容器,是需要将允许过程产生的文件永久保留下来并备份到其他设备上。例如,数据的数据文件不可能保存到容器内部。这是我们可以通过参数-v
经宿主机的一个目录映射到容器内部。
这个过程有点类似于linux上挂载磁盘,在容器内部可以正常读写这个目录,但实际上文件会保存到宿主机上。及时删除了容器,重新创建并运行,也不会丢失文件。
Docker 实践
Docker 是一个开源的商业产品,有两个版本:社区版(Community Edition,缩写为 CE)和企业版(Enterprise Edition,缩写为 EE)。企业版包含了一些收费服务,个人开发者一般用不到。下面以MacOSX系统为案例来演示Docker的编译、创建和运行的过程。
1、Docker安装
常见社区版Docker下载链接:
安装完成后,运行下面的命令,验证是否安装成功。
MAC 用户可以通过 brew install docker
来安装。
$ docker version
# 或者
$ docker info
一般显示如下表示安装成功:
2、Docker的编译
我们基于如下的Dockerfile文件来编译一个镜像:
FROM php:7.2.6-fpm-alpine
MAINTAINER Minho <longfei6671@163.com>
ADD conf/php.ini /usr/local/etc/php/php.ini
ADD conf/www.conf /usr/local/etc/php-fpm.d/www.conf
ENV IMAGICK_VERSION 3.4.2
#Alpine packages
RUN apk add --update git make gcc g++ imagemagick-dev \
libc-dev \
autoconf \
icu-dev \
openldap-dev \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
libmcrypt-dev \
libpcre32 \
bzip2 \
libbz2 \
bzip2-dev \
libmemcached-dev \
cyrus-sasl-dev \
binutils \
&& rm -rf /var/cache/apk/*
RUN apk update && apk add ca-certificates && \
apk add tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
RUN apk update && \
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS openldap-dev && \
docker-php-ext-install ldap && \
apk del .build-deps && \
rm -rf /tmp/* /var/cache/apk/*
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd \
&& docker-php-ext-install mysqli \
&& docker-php-ext-install bz2 \
&& docker-php-ext-install zip \
&& docker-php-ext-install pdo \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install opcache \
&& echo "extension=memcached.so" > /usr/local/etc/php/conf.d/memcached.ini \
&& echo "extension=redis.so" > /usr/local/etc/php/conf.d/phpredis.ini \
&& echo "extension=phalcon.so" > /usr/local/etc/php/conf.d/phalcon.ini \
&& echo "extension=igbinary.so" > /usr/local/etc/php/conf.d/igbinary.ini \
&& echo "extension=bcmath.so" > /usr/local/etc/php/conf.d/bcmath.ini
WORKDIR /usr/src/php/ext/
RUN git clone -b php7-dev-playground1 https://github.com/igbinary/igbinary.git && \
cd igbinary && phpize && ./configure CFLAGS="-O2 -g" --enable-igbinary && make install && \
cd ../ && rm -rf igbinary
# Compile Memcached
RUN git clone -b php7 https://github.com/php-memcached-dev/php-memcached.git && \
cd php-memcached && phpize && ./configure && make && make install && \
echo "extension=memcached.so" > /usr/local/etc/php/conf.d/phpredis.ini && \
cd .. && rm -rf php-memcached
ENV PHPREDIS_VERSION=3.0.0
RUN set -xe && \
curl -LO https://github.com/phpredis/phpredis/archive/${PHPREDIS_VERSION}.tar.gz && \
tar xzf ${PHPREDIS_VERSION}.tar.gz && cd phpredis-${PHPREDIS_VERSION} && phpize && ./configure --enable-redis-igbinary && make && make install && \
echo "extension=redis.so" > /usr/local/etc/php/conf.d/phpredis.ini && \
cd ../ && rm -rf phpredis-${PHPREDIS_VERSION} ${PHPREDIS_VERSION}.tar.gz
ENV YAF_VERSION=3.0.6
WORKDIR /usr/src/php/ext/
# Compile Phalcon
RUN set -xe && \
curl -LO https://github.com/laruence/yaf/archive/yaf-3.0.6.tar.gz && \
tar xzf yaf-3.0.6.tar.gz && cd yaf-yaf-3.0.6 && phpize && ./configure --with-php-config=/usr/local/bin/php-config && make && make install && \
cd ../.. && rm -rf yaf-3.0.6.tar.gz yaf-yaf-3.0.6
ADD conf/yaf.ini /usr/local/etc/php/conf.d/yaf.ini
RUN docker-php-source extract \
&& cd /usr/src/php/ext/bcmath \
&& phpize && ./configure --with-php-config=/usr/local/bin/php-config && make && make install \
&& make clean \
&& docker-php-source delete
这个Dockerfile文件很复杂,是我用来编译开发环境的,因此安装了比较多的扩展,同时还安装了YAF v3.0.6版本。
简单解析如下:
FROM
表示这个镜像是基于已存在的php:7.2.6-fpm-alpine
镜像再次编译的。一般情况下每个Dockerfile文件都会有FROM
指令,来指定基础镜像。MAINTAINER
用来定义这个镜像的作者信息。ADD
用来将编译时物理机上的一些文件或目录添加到镜像内部。比较容易混淆的是COPY
命令,也是将物理机的文件复制到镜像内部。区别请参考 原Dockerfile中的COPY和ADD指令详解与比较ENV
定义环境变量,如果一个Dockerfile文件中多次用到了相同的值,可以统一定义一个环境变量来复用。RUN
这是Dockerfile的关键指令,用来运行一个linux命令,运行后的结果会影响镜像。如文中,多次使用RUN
命令来安装系统依赖。WORKDIR
为后续的RUN、CMD、ENTRYPOINT指令配置工作目录,有点类似于linux命令cd
。EXPOSE
这个指令指示镜像启动后使用的端口,在做端口映射时可以将这个端口映射到宿主机。CMD
用来执行一个脚本或程序,一般会出现在Dockerfile文件最后。
最后两个指令文中并没有用到,应为基础指令中已经使用了。基础的镜像文件可以参考 官方PHP Dockerfile。
有了Dockerfile文件,我们可以用一下命令来编译这个镜像:
docker build -t php7.2.6-fpm-alpine:v1 .
其中-t
用来编译后镜像的名称和标签, 后面的.
表示在当前目录编译,也就是Dockerfile文件所在目录。
编译完成后,执行docker images
会看到系统已存在的镜像:
其中镜像名称或标签为<none>
表示这个镜像没有命名或打标签。
如果想从第三方仓库拉取需要执行:
docker pull registry.cn-hangzhou.aliyuncs.com/lifei6671/php72-fpm-yaf-alpine:latest
这样就可以从第三方仓库拉取镜像,建议使用阿里云或DaoCloud的镜像服务。
想要删除镜像,可以执行docker rmi [镜像名+标签或镜像ID]
。
3、创建并启动容器
一般情况下,在命令行中可以直接创建并启动这个容器,命令如下:
docker run --name php72 -p 9000:9000 -v /Users/minho/wx_dir:/var/www/html -d registry.cn-hangzhou.aliyuncs.com/lifei6671/php72-fpm-yaf-alpine:latest
这条命令是将从远程拉取的镜像创建一个名为php72
的容器,并运行起来。其中,对外映射的端口号是9000
,并将宿主机的目录/Users/minho/wx_dir
映射到容器内部/var/www/html
,-d
是让容器后台运行。
然后执行docker ps
可查看后台运行的容器:
图示中,Nginx的容器对外公开了两个端口443
,80
。
如果查看所有容器,包括已经停止的,可以执行docker ps -a
。
4、停止容器
想停止已存在的容器,可以执行:
docker stop [容器名称或容器ID]
5、删除容器
不在使用的容器可以执行:
docker rm [容器名称或容器ID]
6、查看容器日志
docker logs -f [容器名称或容器ID]
-f
参数是持续的从容器中获取日志,控制台会一直滚动。
7、进入容器
有时候需要进入到容器你不来调试一些问题,可以通过命令实现:
docker exec -i -t docker_nginx-docker_1 /bin/sh
Docker Compose 实践
1、Docker Compose 简介
想想一个常见,我们要运行一个PHP应用,基本上需要三个镜像:php-fpm
、mysql
、nginx
。如果再想用上redis,则还会累加镜像的数量,这么多镜像需要一个一个敲命令拉取、创建、运行,也是一个很大的工作量。
而docker compose是官方提供的一个镜像编排工具。可以通过 YAML 来定义多个镜像,通过命令可以自动拉取创建并运行镜像。
2、Docker Compose 安装
Docker Compose 是单独发行的一个工具,可以从GitHub上下载到编译版本: https://github.com/docker/compose/releases 。
MAC 用户在安装Docker时会自动安装该工具,可以使用docker-compose version
检查是否安装,如果未安装请下载对应的版本安装。
3、Docker Compose 文件实例
Docker Compose 命令的执行是需要有一个配置文件,一般是yml格式的,文件格式如下:
nginx-docker:
image: daocloud.io/lifei6671/docker-nginx:latest
volumes:
- ~/docker/nginx/conf.d:/etc/nginx/conf.d
ports:
- "80:80"
- "403:403"
links:
- php56phalcon:php56phalcon
- php7yaf
- redis_docker
- mysql57
- memcached_docker
volumes_from:
- php56phalcon
expose:
- 80
- 403
dns:
- 223.5.5.5
php56phalcon:
image: daocloud.io/lifei6671/php56-fpm-phalcon-alpine:latest
volumes:
- ~/wx_Lifeilin:/mnt/hgfs
ports:
- "8000:9000"
links:
- mysql57
- redis_docker
- memcached_docker
expose:
- 9000
dns:
- 223.5.5.5
php7yaf:
image: registry.cn-hangzhou.aliyuncs.com/lifei6671/php72-fpm-yaf-alpine:latest
volumes:
- ~/wx_Lifeilin:/mnt/hgfs
ports:
- "9000:9000"
links:
- mysql57
- redis_docker
- memcached_docker
expose:
- 9000
dns:
- 223.5.5.5
extra_hosts:
- "sso.minho.com:10.200.10.1"
- "rbac.minho.com:10.200.10.1"
mysql57:
image: daocloud.io/library/mysql:5.7.14
volumes:
- ~/docker/mysql:/var/lib/mysql
ports:
- "3306:3306"
expose:
- 3306
environment:
- MYSQL_ROOT_PASSWORD=123456
dns:
- 223.5.5.5
redis_docker:
image: daocloud.io/library/redis:latest
volumes:
- ~/docker/redis:/data
ports:
- "6379:6379"
expose:
- 6379
environment:
- REDIS_PASS=**None**
memcached_docker:
image: daocloud.io/library/memcached:1.4.27-alpine
ports:
- "11211:11211"
expose:
- 11211
dns:
- 223.5.5.5
这是我开发中实际的配置信息。其中包含了Nginx、PHP7.2、PHP5.6、MySQL5.7、Redis以及memcached的1.4版本。
配置中,我是将本机的~/docker/
和~/wx_Lifeilin
目录映射到了容器内,实际使用中,请跟进开发环境进行配置。
使用时,在命令中定位到 docker-compose.yml 文件所在目录执行:
docker-compose up -d
这样docker-compose会自动pull下来镜像,并创建对应容器,之后启动。
4、删除和停止镜像
删除:
docker-compose rm
停止:
docker-compose stop
停止并删除:
docker-compose -f docker-compose.yml down
5、查看日志
docker-compose logs -f
更多命令可以通过docker-compose help
或参考官方文档: https://docs.docker.com/compose/gettingstarted