DanceSmile / dancesmile.github.io

dancesemile's github pages
4 stars 1 forks source link

使用Docker快速构建生产环境 #2

Open DanceSmile opened 6 years ago

DanceSmile commented 6 years ago

Docker 简介

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术

由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器

Docker 在容器的基础上,进行了进一步的封装,从文件系统网络互联进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。

安装

# step 1: 安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# Step 2: 添加软件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# Step 3: 更新并安装 Docker-CE
sudo yum makecache fast
sudo yum -y install docker-ce
# Step 4: 开启Docker服务
sudo service docker start

镜像

镜像是 Docker 中最核心的概念,一个容器可以理解为镜像的实例化

Dockerfile 指令集

COPY 复制文件

COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。 <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

COPY package.json /usr/src/app/

ADD 更高级的复制文件

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。

CMD 容器启动命令

容器就是进程 ,既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器 主进程 的启动命令的。

CMD 有两种格式:

如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:

CMD echo $HOME

在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

使用 nginx 以后台守护进程形式启动 nginx 服务

CMD service nginx start

在实际执行中,会将其变更为:

CMD sh -c service nginx start

这样将导致sh执行完后sh主进程退出,导致容器退出 正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 入口点

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令

ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]  <CMD>

ENV 设置环境变量 格式有两种:

定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码:

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

VOLUME 定义匿名卷 格式为:

VOLUME /data

这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。

docker run -d -v mydata:/data xxxx

EXPOSE 声明端口

EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

WORKDIR 指定工作目录

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

USER 指定当前用户

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

容器

容器可以理解为独立运行的一个或者一组应用,它完全隔离和宿主主机的环境。

在运行时可以指定新的命令来替代镜像设置中的这个默认命令

sudo docker run -it  unbuntu cat /etc/os-release

新建并启动容器

sudo docker  run --name web   nginx 

后台新建并启动容器 在使用 -d 参数时,容器启动后会进入后台。

sudo docker  run --name  web -d   nginx 

注: 容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

交互式运行容器

sudo docker  run --name centos -it  centos bash  

其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。

停止容器

sudo docker container web   stop

启动容器

sudo docker container web  start

要获取容器的输出信息,可以通过 docker container logs 命令。

进入容器

docker exec -it web bash

删除容器

sudo docker container rm web 

删除所有停止状态的容器

sudo docker container prune

仓库

仓库(Repository)是集中存放镜像的地方。

Docker Hub

前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 15,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现

登录 Docker Hub 可以通过执行 docker login 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。 你可以通过 docker logout 退出登录。

拉取镜像 你可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。

推送镜像 用户也可以在登录后通过 docker push 命令来将自己的镜像推送到 Docker Hub。

$ docker tag ubuntu:17.10 username/ubuntu:17.10

$ docker image ls

REPOSITORY                                               TAG                    IMAGE ID            CREATED             SIZE
ubuntu                                                   17.10                  275d79972a86        6 days ago          94.6MB
username/ubuntu                                          17.10                  275d79972a86        6 days ago          94.6MB

$ docker push username/ubuntu:17.10

$ docker search username

NAME                      DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
username/ubuntu

网络

Docker允许外部网络访问到容器内部。同样也可以使用容器互联的方式实现各个容器之间的访问。

外部网络访问容器

Docker容器中运行的应用,如果想要通过外部访问这个应用,可以使用-P 或者 -p 将容器应用开放的端口映射到主机的端口上面。外部就可以通过访问宿主的ip加上映射的端口来访问容器内的应用了。


当使用 -P 标记时,Docker 会随机映射一个的端口到内部容器开放的网络端口。

[vagrant@localhost ~]$ sudo docker run  --name nginx1 -d -P nginx
d4d7abd0a5b10e3ae6f3783fccadd3122960bb9190c29753ee894dc92bb94f3b
[vagrant@localhost ~]$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
d4d7abd0a5b1        nginx               "nginx -g 'daemon of…"   48 seconds ago      Up 47 seconds       0.0.0.0:32769->80/tcp   nginx1

-p 则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。

# 制定映射到主机的端口为80,绑定主机上所有的IP
sudo docker  run    --rm  -p 80:80 nginx
# 制定映射到主机的端口为80,绑定主机上指定IP 127.0.0.1
sudo docker  run    --rm  -p 127.0.0.1:80:80 nginx
# 制定映射到主机的随机闲置端口,绑定主机上指定IP 127.0.0.1
sudo docker  run    --rm  -p 127.0.0.1::80 nginx

-p 标记可以多次使用来绑定多个端口

$ docker run -d \
    -p 5000:5000 \
    -p 3000:80 \
    training/webapp \
    python app.py
容器互联

创建一个新的 Docker 网络

[vagrant@localhost ~]$ sudo docker  network create -d bridge my_network
89ba79b8b48d0cab00545154cd87f5502ea5c8157d0a3df18f76de95880979ee

查看所有的容器网络

[vagrant@localhost ~]$ sudo docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
a51998aeaa00        bridge              bridge              local
d8463e2bd0fe        host                host                local
89ba79b8b48d        my_network          bridge              local
84d05dcc187f        none                null                local

-d 参数指定 Docker 网络类型,有 bridge overlay。其中 overlay 网络类型用于 Swarm mode,在本小节中你可以忽略它。


运行容器 并且 指定容器网络 下面的操作证明同属于my_network网络 容器centos1 和容器centos1 是容器互联的状态

[vagrant@localhost ~]$ sudo docker run -it --rm --name centos1 --network my_network centos bash
[root@a6c7797239a0 /]# ping centos2
PING centos2 (172.19.0.2) 56(84) bytes of data.
64 bytes from a6c7797239a0 (172.19.0.3): icmp_seq=1 ttl=64 time=0.054 ms
64 bytes from a6c7797239a0 (172.19.0.3): icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from a6c7797239a0 (172.19.0.3): icmp_seq=3 ttl=64 time=0.031 ms
[vagrant@localhost ~]$ sudo docker run -it --rm --name centos2 --network my_network centos bash
[root@a6c7797239a0 /]# ping centos1
PING centos1 (172.19.0.2) 56(84) bytes of data.
64 bytes from a6c7797239a0 (172.19.0.2): icmp_seq=1 ttl=64 time=0.054 ms
64 bytes from a6c7797239a0 (172.19.0.2): icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from a6c7797239a0 (172.19.0.2): icmp_seq=3 ttl=64 time=0.031 ms
查看端口映射情况

sudo docker port container_name

sudo docker inspect container_name

加入和移除网络

加入网络

# 移除容器的网络
docker network disconnect   web web
# 加入容器到网络
docker  network  connect  web web

存储

Docker 容器管理数据主要有两种方式

数据卷volumes

数据卷有很多特性

  1. 数据卷可以在多个容器之间共享
  2. 数据卷修改会立马生效
  3. 数据卷是独立存在的,不会因为容器删除而被删除
  4. 数据卷的更新不会影响镜像

数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

创建数据卷

[vagrant@localhost ~]$ sudo docker volume create my_volume
my_volume

查看所有数据卷

[vagrant@localhost ~]$ sudo docker volume ls
DRIVER              VOLUME NAME
local               my_volume

查看特定的数据卷的详细信息

[vagrant@localhost ~]$ sudo docker volume inspect  my_volume
[
    {
        "CreatedAt": "2018-02-08T16:52:56Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my_volume/_data",
        "Name": "my_volume",
        "Options": {},
        "Scope": "local"
    }
]

挂在数据卷到容器

[vagrant@localhost ~]$ sudo docker run --name web1 -v my_volume:/usr/share/nginx/html -d nginx
e5f017eb4055f9d4d5ae0989c3e2893820cfe9292c0d38ac411f7611a1229cda
[vagrant@localhost ~]$ sudo docker run --name web2 -v my_volume:/usr/share/nginx/html -d nginx
4943f3b685e7ccef352e2127590c0ca82204ab687dd2379a1e6548dcbd700066

上述实例的两个容器同时挂载了my_volume数据卷,所以他们的数据是共享的

[vagrant@localhost ~]$ sudo docker exec -it web1 bash
root@e5f017eb4055:/# cd /usr/share/nginx/html/
root@e5f017eb4055:/usr/share/nginx/html# echo "test_share_volume"  >> test.html
root@e5f017eb4055:/usr/share/nginx/html# cat test.html
test_share_volume
[vagrant@localhost ~]$ sudo docker exec -it web2 bash
root@e5f017eb4055:/# cd /usr/share/nginx/html/
root@e5f017eb4055:/usr/share/nginx/html# echo "test_share_volume"  >> test.html
root@e5f017eb4055:/usr/share/nginx/html# cat test.html
test_share_volume

随机存储位置 -v

docker run  -v /mnt  --name db  centos

也可以使用inspect查看当前容器的数据卷

[vagrant@localhost ~]$ sudo docker inspect web1
 "Mounts": [
            {
                "Type": "volume",
                "Name": "my_volume",
                "Source": "/var/lib/docker/volumes/my_volume/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ]

删除数据卷

[vagrant@localhost ~]$ sudo docker volume rm my_volume
Error response from daemon: unable to remove volume: remove my_volume: volume is in use - [e5f017eb4055f9d4d5ae0989c3e2893820cfe9292c0d38ac411f7611a1229cda, 4943f3b685e7ccef352e2127590c0ca82204ab687dd2379a1e6548dcbd700066]

当数据卷在使用中的时候删除会出现警告

数据卷是用来做数据持久化的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

无主的数据卷可能会占据很多空间,要清理请使用以下命令

$ docker volume prune
挂在主机目录

挂在主机上的本地目录到指定的容器

[vagrant@localhost ~]$ sudo docker  run  --name  volume_local  -v $HOME/volume_local:/usr/share/nginx/html  -p 80:80 -d nginx

挂在当前的加目录的文件夹到nginx的目录,可以在本地修改代码同步到容器

数据容器 --volumes-from
sudo docker run --name web  --volume-from <container> centos

docker-compose

安装

sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

启动项目 后台

docker-compose  up -d  -p project_name

实战 lnmp

https://github.com/DanceSmile/docker_php_run_context

swarm 集群

结语

Docker 简化了部署环境,平衡了不同平台之间的差异。

扩展阅读

Docker100问