一、环境搭建(Ubuntu 18.04)

本文前期Docker环境为:Windows10 1809 企业版 + VMware Workstation 15 Pro + Ubuntu 18.04.2LTS

后期Kubernetes环境为:Windows10 1809 企业版 + Vagrant 2.2.7 + VirtualBox 6.1.4 + centos7/Ubuntu 18.04 LTS

Vagrant+VirtualBox

下载Vagrant安装包:官网链接

下载VirtualBox安装包:官网链接,不过VirtualBox下载需要梯子,可以通过清华镜像下载

vagrant的相关image可以通过该网址进行搜索

上述两个软件均傻瓜式安装即可,可以通过下面的方法利用Vagrant在VirtualBox中创建虚拟机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#查看Vagrant版本
vagrant -v

#创建目录
mkdir centos7

####################创建centos 7的镜像#############################
#初始化一个centos7的Vagrant file(类似于makefile)
vagrant init centos/7
#创建centos7的虚拟机,此处会下载image,时间较久
vagrant up

####################创建ubuntu的镜像#############################
#初始化一个Ubuntu 18.04 LTS的Vagrant file
vagrant init ubuntu/bionic64
#创建Ubuntu 18.04 LTS的虚拟机,此处会下载image,时间较久
vagrant up

####################国内镜像源 创建ubuntu的镜像#############################
#初始化一个Ubuntu 18.04 LTS的Vagrant file
vagrant init ubuntu/bionic64
#采用清华镜像
vagrant box add https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/bionic/current/bionic-server-cloudimg-amd64-vagrant.box --name ubuntu/bionic64
#创建Ubuntu 18.04 LTS的虚拟机,此处会下载image,时间较久
vagrant up
  • 注意:如需换成其他镜像需要自己去清华镜像(有可能没有)中查找对应的box文件
  • 也可以采取用其他方式下载box文件(如百度网盘),再通过命令行添加,详细方法即相关地址可以参考这篇博客
  • 如果Windows用户名是中文,则可能在vagrant up这一步报incompatible character encodings: GBK and UTF-8 (Encoding::CompatibilityError)类似错误,具体方法详见这篇博客,概括为以下几步:
    • 设置环境变量VAGRANT_HOME为不包含中文的路径,该环境变量是用于保存Vagrant下载的box镜像文件(具体路径为$(VAGRANT_HOME)/.vagrant.d
    • 修改VirtualBox中全局配置选项中的 默认虚拟电脑位置VirtualBox VMs的路径 ,不能包含中文名
  • 注意Vagrant和VirtualBox版本需要匹配,有的版本不匹配可能会报各种错误,本文中测试一切正常
  • 默认Vagrant当前配置为在启用SharedFoldersEnableSymlinksCreate选项的情况下创建VirtualBox同步的文件夹。 如果不信任Vagrant访客,则可能要禁用此选项。 有关此选项的更多信息,请参考VirtualBox手册
    • 可以通过环境变量全局禁用此选项:VAGRANT_DISABLE_VBOXSYMLINKCREATE = 1
    • 在Vagrantfile中配置如下代码:config.vm.synced_folder '/host/path', '/guest/path', SharedFoldersEnableSymlinksCreate: false

通过Vagrant命令进入centos7中的shell:

1
vagrant ssh

如果想通过其他方式登录虚拟机(如:Xshell),可以通过如下命令获取hostname、port、IdentityFile三个配置信息:

1
2
3
4
5
6
7
8
9
10
11
E:\Ubuntu>vagrant ssh-config
Host default
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile E:/Ubuntu/.vagrant/machines/default/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL

其中:HostName为主机IP地址,Port为开放的端口,IdentityFile为Public Key秘钥文件的路径,依次填入其他软件的相应设置中即可访问该虚拟机

另外,该虚拟机默认登录账号用户名为vagrant,密码为vagrant,root账号密码为vagrant。也有可能没有root密码,需要通过sudo passwd命令重设密码

附:ubuntu18.04换清华源:

编辑/etc/apt/sources.list文件,修改为以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse

# 预发布软件源,不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse

安装Docker Engine - Community

官方文档教程详见:Install Docker Engine on Ubuntu | Docker Documentation

设置源

1
2
3
4
5
6
7
8
9
10
11
12
13
#如果有之前的版本 需要删除
sudo apt-get remove docker docker-engine docker.io containerd runc

#更新
sudo apt-get update
#安装依赖
sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release
#添加Docker官方GPG秘钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
#添加Docker源
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
#建议采用国内源(docker ce):
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.bfsu.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

由于Docker hub源不稳定,推荐国内源:

ustc帮助文档:https://lug.ustc.edu.cn/wiki/mirrors/help/docker

1
2
3
4
5
6
7
8
sudo vi /etc/docker/daemon.json
#添加以下内容 注意不能是https 否则最后一点下载不下来
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/","https://hub-mirror.c.163.com","https://registry.docker-cn.com"],
......
}
#重启docker服务
service docker restart
  • 可以通过docker info查看是否设置成功

安装

1
2
3
4
5
6
7
#安装Docker Engine - Community
sudo apt-get update
#安装最新版
sudo apt-get install docker-ce docker-ce-cli containerd.io
#验证
sudo docker version
sudo docker run hello-world

或者自己选择版本安装

1
2
3
apt-cache madison docker-ce

sudo apt-get install docker-ce=<VERSION_STRING> docker-ce-cli=<VERSION_STRING> containerd.io

卸载Docker Engine - Community

1
2
3
sudo apt-get purge docker-ce
#删除所有镜像,容器和卷(volumes):
sudo rm -rf /var/lib/docker

nvidia docker

NVIDIA于2016年开始设计NVIDIA-Docker以便于容器使用NVIDIA GPUs。 第一代nvidia-docker1.0实现了对docker client的封装,并在容器启动时,将必要的GPU device和libraries挂载到容器中。但是这种设计的方式高度的与docker运行时耦合,缺乏灵活性:

  • 设计高度与docker耦合,不支持其它的容器运行时。如: LXC, CRI-O及未来可能会增加的容器运行时。
  • 不能更好的利用docker生态的其它工具。如: docker compose。
  • 不能将GPU作为调度系统的一种资源来进行灵活的调度。
  • 完善容器运行时对GPU的支持。如: 自动的获取用户层面的NVIDIA Driver libraries, NVIDIA kernel modules, device ordering等。

基于上面描述的这些弊端,NVIDIA开始了对下一代容器运行时的设计: nvidia-docker2.0

nvidia-docker2.0中核心部分是nvidia-container-runtime,在原有的docker容器运行时runc的基础上增加一个prestart hook,用于调用libnvidia-container库

RunC 是一个轻量级的工具,用来运行容器。可以认为它是个命令行小工具,可以不用通过 docker 引擎,直接运行容器。

事实上,runC 是标准化的产物,它根据 OCI 标准来创建和运行容器。而 OCI(Open Container Initiative)组织,旨在围绕容器格式和运行时制定一个开放的工业化标准。

直接使用RunC的命令行即可以完成创建一个容器,并提供了简单的交互能力。

正常创建容器流程:docker --> dockerd --> containerd--> containerd-shim -->runc --> container-process

创建gpu容器:docker--> dockerd --> containerd --> containerd-shim--> nvidia-container-runtime --> nvidia-container-runtime-hook --> libnvidia-container --> runc -- > container-process

安装

参考链接:

配置要求:

  1. Linux内核版本 > 3.10
  2. Docker版本 >= 19.03
  3. NVIDIA GPU架构最低需求 >= Kepler
  4. NVIDIA Linux 驱动 >= 418.81.07

根据上述章节安装完docker后即可安装nvidia docker。nvidia docker分为两个版本,推荐安装nvidia docker2

1
2
#添加nvidia官方GPG秘钥
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) && curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
  • 这里在Ubuntu20.04上最后安装的源是用的18.04的,可能是官方还未修改
1
2
3
# 安装nvidia docker2
sudo apt-get update
sudo apt-get install -y nvidia-docker2
  • 如果在之前换过docker hub源,这里在安装nvidia docker2是会报一个警告,直接按y同意替换即可,之后再手动将源修改回来
1
2
3
# 重启并验证nvidia docker2
sudo systemctl restart docker
sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
  • nvidia docker提供三种类型的镜像:
    • base:包括cuda(cudart)
    • runtime:base+cuda数学库+NCCL+cuDNN
    • devel:runtime+各种cuda头文件+cuda开发工具包

环境变量设置

可以通过设置特定的环境变量或者CLI中传入特定参数使用

gpu数量

用户能够通过系统环境变量NVIDIA_VISIBLE_DEVICES或者docker CLI的--gpu选项指定gpu:

变量值 描述
0,1,2,GPU-fef8089b GPU UUID 或索引列表
all 所有gpu均可用,默认值
none 所有gpu均不可用,但是驱动仍正常使用
void or empty or unset nvidia-container-runtime 将与 runc行为一样 (如:没有gpu也没有驱动)
  • 使用环境变量时,需要添加--runtime=nvidia参数,否则将使用默认参数
1
2
3
4
5
6
7
8
9
10
11
12
# 使用--gpus参数
docker run --rm --gpus all nvidia/cuda nvidia-smi
docker run --gpus '"device=1,2"' nvidia/cuda nvidia-smi --query-gpu=uuid --format-csv
# 用环境变量
docker run --rm --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all nvidia/cuda nvidia-smi
docker run --rm --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=1,2 nvidia/cuda nvidia-smi --query-gpu=uuid --format=csv
# 使用uuid
nvidia-smi -i 3 --query-gpu=uuid --format=csv # 查询uuid
docker run --gpus device=GPU-18a3e86f-4c0e-cd9f-59c3-55488c4b0c24 nvidia/cuda nvidia-smi

# 在两块GPU上启动GPU的容器
docker run --rm --gpus 2 nvidia/cuda nvidia-smi
驱动

设置环境变量NVIDIA_DRIVER_CAPABILITIES,或--gpus参数即可:

变量值 描述值
compute,videographics,utility 容器需要的驱动程序功能的逗号分隔列表
all 使能全部功能
empty or unset 默认utility

下面提供了支持的驱动程序功能:

驱动参数 描述
compute CUDA + OpenCL
compat32 32位程序
graphics OpenGL + Vulkan
utility nvidia-smi 和 NVML
video Video Codec SDK
display X11
1
2
3
# 需要CUDA 和 NVML 能力
docker run --rm --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=2,3 -e NVIDIA_DRIVER_CAPABILITIES=compute,utility nvidia/cuda nvidia-smi
docker run --rm --gpus 'all,"capabilities=compute,utility"' nvidia/cuda:11.0-base nvidia-smi
资源限制

通过如下的一个逻辑表达式,定义容器上软件版本或 GPU 架构的约束。

基本形式为NVIDIA_REQUIRE_*,支持以下几种:

约束 描述
cuda 限制CUDA版本
driver 限制驱动版本
arch 限制gpu的架构
brand 限制gpu的型号(如:GeForce, Tesla, GRID)
1
2
# 限制驱动与cuda版本
NVIDIA_REQUIRE_CUDA "cuda>=11.0 driver>=450"

NVIDIA_DISABLE_REQUIRE宏则会禁用上述限制

二、Docker的镜像和容器

架构和底层

  • Docker是一个平台,提供一个开发、打包、运行app的平台
  • 把app和底层设备隔离开

Docker架构

Docker Engine:

  • 后台进程(dockerd)
    • 提供 REST API 的服务(Server)
      • 提供 CLI(client,客户端)
    • C-S架构

Docker Engine

整体架构:

整体架构

  • Client:Docker提供的命令(可以和host在一台机器上)
  • Docker Host:启动了dockerd的机器
    • images
    • containers
  • Registry:库,类似于GitHub

docker底层技术支持:

  • Namespaces:隔离pid、net、ipc、mnt、uts
  • Control groups:做资源限制
  • Union file systems:Container和 nimage的分层

Image

官方命令手册:https://docs.docker.com/engine/reference/commandline/image/

  • 文件和 meta data的集合( root filesystem)
  • 分层的,并且每一层都可以添加改变删除文件,成为一个新的image
  • 不同的image可以共享相同的 layer
  • Image本身是read-only的

Dockerfile:通过该文件定义一个image并能够构建该image

BaseImge:直接基于Linux的内核,在内核上制作的一个镜像(如各种Linux的发行版:ubuntu、centos等),可以在该镜像上在制作一个新的image

image-20200205000255254

Container

官方命令手册:https://docs.docker.com/engine/reference/commandline/container/

  • 通过image创建(copy)
  • 在image layer之上建立一个container layer(可读写)
  • 类比面向对象:类(image)和实例(container)
  • Image负责app的存储和分发, Container负责运行app

Container

构建自己的image

1
2
3
4
5
6
7
8
#基于container(xxx,名字,name)创建一个image(yyy)  不提倡,不安全
sudo docker container commit xxx yyy
sudo docker commit xxx yyy
#从dockerfile构建一个image
sudo docker image build -t tag(docker的tag) .(基于那个目录构建)
sudo docker build -t tag(docker的tag) .(基于那个目录构建)
#例子
sudo docker build -t dockerusernull/flask-hello-world /home/null/Code/docker/helloworld/

调试

每次构建的时候,在每一层都会生成临时容器并通过-->显示出该容器的id(docker images也可以,注意观察生成时间),可以通过

1
sudo docker run -it 临时容器id /bin/bash

有时候会因为升级docker镜像中的软件失败而导致镜像生成失败,此时通过进入docker内部更换清华源即可

运行

1
2
3
4
5
6
7
8
9
10
sudo docker run image名字
sudo docker run -d image名字#后台运行
sudo docker run -d --name=demo image名字#后台运行 并指定一个名字 否则会自动分配
#停止contaniner
sudo docker container stop id
sudo docker stop id
#启动contaniner
sudo docker start id或名字
#例子
sudo docker run dockerusernull/flask-hello-world
  • --name指定的名字具有唯一性,可以替代id用于别的命令
  • docker start:只要不删除,该命令就能够重新启动stop的容器

exec:进运行中的容器内部,看具体的细节

1
2
sudo docker exec -it 容器id /bin/bash#exec:对运行中的容器执行的命令(/bin/bash) -it:交互式的执行
sudo docker exec -it 容器id ip a#打印运行中的容器的ip地址
  • 不止运行/bin/bash,还可运行其他命令,如:python等

DockerFile

官方文档:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

  • FROM:选择base image,在该base image上构建新的image

    • FROM scratch:从头制作base image
    • 尽量使用官方的image作为 base image
  • LABEL:定义image的metadata(类似代码中的注释)

    •   LABEL maintainer="xiaoquwl@gmail.com"
        LABEL version="1.0"
        LABEL description="This is description"
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      * 不可少,能够让人更加明白

      * `RUN`:运行命令,每一次RUN都会使image生成新的一层

      * 为了美观,复杂的RUN请用反斜线换行

      * 避免无用分层,合并多条命令成一行

      * ```dockerfile
      RUN yum update && yum install -y vim \
      python-dev#反斜线换行
      RUN apt-get update && apt-get install -y perl \
      pwgen --no-install-recommends && rm -rf \
      /var/ib/apt/ists/*#注意清理 cache
      RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
  • WORKDIR:设定当前工作目录(类似于cd)

    •   WORKDIR /test#如果没有会自动创建test目录
        WORKDIR demo
        RUN pwd#输出结果应该是/test/demo
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      * 用 `WORKDIR`,不要用 `RUN cd`

      * 尽量使用绝对目录

      * `ADD`和`COPY`:把本地的一些文件添加到docker image中

      * ADD除了拷贝的功能外还能够解压缩

      * 大部分情况,COPY优于ADD

      * 添加远程文件/目录请使用curl或者wget

      * ```dockerfile
      ADd hello /#简单拷贝
      ADD test.tar.gz /#添加到根目录并解压
      #WORKDIR和ADD联合使用
      WORKDIR /root
      ADD hello test/ #hello文件位置/root/test/hello
      WORKDIR /root
      COPY hello test/ #hello文件位置/root/test/hello
  • ENV:设置一个环境变量或常量

    • 尽量使用ENV增加可维护性

    •   ENV MYSQL_VERSION 5.6#设置常量
        RUN apt-get install -y mysql-server="${MYSQL_VERSION}" \
        	&& rm -rf /var/lib/apt/lists/*#引用常量
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      * `VOLUME`和`EXPOSE`:主要用于存储和网络

      * `VOLUME`:指定在容器中某一个目录中产生的数据,同时挂载到Linux主机中的某一个目录上,并且会创建一个docker volume的对象
      * `EXPOSE`:会将运行中的container中的端口暴露出来

      * shell和exec格式

      * shell格式:后面是shell命令

      * ```dockerfile
      RUN apt-get install -y vim
      CMD echo "hello docker"
      ENTRYPOINT echo "hello docker"
    • exec格式:需要根据特点的格式指出命令和参数

      •   #XXX ["命令","参数",...]
          RUN ["apt-get","install","-y","vim"]
          CMD ["/bin/echo","hello docker"]
          ENTRYPOINT ["/bin/echo","hello docker"]
          #以下两种输出结果不一样
          ENV name Docker
          ENTRYPOINT ["/bin/echo","hello $name"]#hello $name
          ENTRYPOINT ["/bin/bash","-c","echo hello $name"]#hello Docker(-c表示后面的事bash的参数)
          
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        * `CMD`和`ENTRYPOINT`:

        * `RUN`:执行命令并创建新的 image Layer

        * `CMD`:设置容器启动后默认执行的命令和参数

        * 容器启动时默认执行的命令
        * 如果 docker run指定了其它命令,CMD命令被忽略
        * 此处docker run指`docker run 镜像名`,再加任意一个参数均会忽略CMD命令,如`docker run -it 镜像名 /bin/bash`
        * 如果定义了多个CMD,只有最后一个会执行

        * `ENTRYPOINT`:设置容器启动时运行的命令

        * 让容器以应用程序或者服务的形式运行

        * 不会被忽略,一定会执行

        * ```dockerfile
        #执行一个shel脚本
        COPY docker-entrypoint.sh /usr/local/bin/
        ENTRYPOINT ["docker-entrypoint sh"]

发布镜像

直接发布镜像:

  • DockerHub网站上注册账号

  • sudo docker login,在本机上通过命令行登录DockerHub

  • sudo docker push 账号名/image名字:tagsudo push 账号名/image名字:tag往DockerHub上推

发布Dockerfile:

  • 在DockerHub上绑定Github账号
  • 在GitHub上分享Dockerfile
  • DockerHub会自动从GitHub上克隆Dockerfile
  • DockerHub后台服务器会自动根据Dockerfile构建image

容器资源限制

不做限制的容器会尽最大可能占用物理机的资源,可以通过docker run命令指定对容器的限制

  • 对memory的限制
    • --memory:若只指定该参数,则swap memory也是同样大小,总共消耗内存为2*memory
      • eg:sudo docker run --memory=200M 容器id
    • --memory-swap:指定swap memory参数
  • 对cpu的限制
    • --cpu-shares:限制相对权重,即每个容器根据该值来分配物理机上的cpu算力
      • sudo docker run --cpu-shares=10 --name=test1 容器id --cpu 1
      • sudo docker run --cpu-shares=5 --name=test2 容器id --cpu 1
      • 上述两个容器会按照test1:test2=2:1占用cpu
    • --cpu:利用编号指定运行的cpu

实战——ubuntu上打包stress

python常驻程序

新建Dockerfile:

1
2
3
4
5
6
7
8
9
FROM python:2.7
LABEL maintainer="NULL"

WORKDIR /pyapp

RUN pip install flask
COPY app.py /pyapp
EXPOSE 5000
CMD python app.py
  • EXPOSE:在此处代表要暴露出去的端口
  • CMD:此处用于表示之后一直运行的程序

ubuntu上打包stress

新建Dockerfile:

1
2
3
4
FROM ubuntu
RUN apt-get updata && apt-get install -y stress
ENTRYPOINT ["/user/bin/stress"]
CMD []
  • ENTRYPOINT:在此处代表要执行的命令
  • CMD:此处用于接受之后docker run后的参数,传递给ENTRYPOINT指定的命令
    • 可以在[]中指定默认的参数

常用命令

全部命令详见官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#使docker前不需要加sudo
sudo groupadd docker#新建一个组
sudo gpasswd -a 用户名 组名#添加用户到组
sudo service docker restart#重启docker

#查看dockerhub上的镜像
sudo docker search image名字
#查看本机上的镜像
sudo docker image ls
sudo docker images
#从Dockerhub上拉取
sudo docker pull ubuntu:14.04
#从Dockerhub上xxx处拉取(第三方)
sudo docker pull ubuntu:14.04/XXX
#运行docker
sudo docker run image名字
sudo docker run -d image名字#-d:后台运行
sudo docker run -d -e PENG=xxxx image名字#后台运行,且设置一个PENG的环境变量,值为xxxx(-e:设置环境变量)
#-it:交互式的运行docker,并进入其控制台
sudo docker run -it image名字
exit #退出交互环境
#查看本机上的在运行的容器(正在运行的,常驻内存)
sudo docker container ls
#查看本机上所有的容器(正在运行的和已经退出的)
sudo docker container ls -a
sudo docker container ls -aq#列出所有的id
sudo docker ps -a
#删除contaniner
sudo docker container rm id
sudo docker rm id
sudo docker rm $(sudo docker container ls -aq)#删除所有
#停止contaniner
sudo docker container stop id
sudo docker stop id
#删除image
sudo docker image rm id
sudo docker rmi id

#本机传输文件到docker container中
sudo docker cp [OPTIONS] 本地文件 container的id:container中的目录
#docker container传输文件到本机中
sudo docker cp [OPTIONS] container的id:container中的目录 本地文件
#将宿主机上的目录挂载到镜像中
docker run -it -v /home/dock/Downloads:/usr/Downloads

#显示容器的详细信息
sudo docker inspect id
#查看容器运行的输出log
sudo docker logs id或--name指定的名字

生命周期管理

run

创建一个新的容器并运行一个命令

1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
  • COMMANDARG为在起来的容器中执行的命令与参数

OPTIONS:

  • -d:后台运行,并返回容器ID
  • -e:设置环境变量
    • --env-file=[]:从指定文件读入环境变量
  • -it:交互运行,并进入其shell
    • -i:以交互模式运行容器
    • -t:为容器重新分配一个伪输入终端
  • --name:为容器指定一个名称(设置别名)
  • 网络参数
    • --net="bridge":指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型,详见 [三、docker网络](# 三、docker网络)
    • --link=[]:添加链接到另一个容器,详见 [docker之间的link](# docker之间的link)
    • -p:端口映射,格式为:主机(宿主)端口:容器端口,详见 [端口映射](# 端口映射)
    • --expose=[]:开放一个端口或一组端口;
    • --dns 8.8.8.8:指定容器使用的DNS服务器,默认和宿主一致
    • --dns-search example.com:指定容器DNS搜索域名,默认和宿主一致;
    • -h "mars":指定容器的hostname;
  • 资源参数
    • --volume-v:绑定一个卷,详见 [四、Docker的持久化存储和数据共享](# 四、Docker的持久化存储和数据共享)
    • --cpuset="0-2"--cpuset="0,1,2":绑定容器到指定CPU运行
    • -m:设置容器使用内存最大值
start/stop/restart

启动、停止、重启容器

1
2
3
docker start [OPTIONS] CONTAINER [CONTAINER...]
docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker restart [OPTIONS] CONTAINER [CONTAINER...]
kill

杀掉一个运行中的容器

1
docker kill [OPTIONS] CONTAINER [CONTAINER...]
  • -s:向容器发送一个信号
rm

删除一个或多个容器

1
docker rm [OPTIONS] CONTAINER [CONTAINER...]
  • -f:通过 SIGKILL 信号强制删除一个运行中的容器
  • -l:同时移除容器间的网络连接,而非容器本身(不加CONTAINER则只删除连接)
  • -v:同时删除与容器关联的卷(不加CONTAINER则只删除卷)
pause/unpause

暂停/恢复容器中所有的进程

1
2
docker pause CONTAINER [CONTAINER...]
docker unpause CONTAINER [CONTAINER...]
create

创建一个新的容器但不启动它(用法同[docker run](# run))

1
docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
exec

在运行的容器中执行命令

1
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
  • -d:后台运行,并返回容器ID
  • -it:交互运行,并进入其shell
    • -i:以交互模式运行容器
    • -t:为容器重新分配一个伪输入终端

容器操作

以下操作全都是docker container打头,可以省略container。一般来说容器有以下几种状态:

  • created(已创建)
  • restarting(重启中)
  • running(运行中)
  • removing(迁移中)
  • paused(暂停)
  • exited(停止)
  • dead(死亡)
ps

列出所有在运行的容器信息

1
docker ps [OPTIONS]
  • -aq:列出所有创建的容器ID

    • -a:显示所有的容器,包括未运行的
    • -q:静默模式,只显示容器编号
  • -f,--filter:根据条件过滤显示的内容

    • # 根据名称过滤
      docker ps --filter "name=test-nginx"
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      * `-l`:显示最近创建的容器

      * `-n`:列出最近创建的n个容器

      * `--no-trunc`:不截断输出(防止信息显示不全)

      * `-s`:显示该容器占用的大小

      ##### inspect

      获取容器/镜像的元数据(即配置时的`.yml`文件)

      ```bash
      docker inspect [OPTIONS] NAME|ID [NAME|ID...]
  • -f:指定返回值的Go模板文件,该模板有相关的语法,具体可以参考docker inspect -f 模版

    1
    2
    3
    4
    5
    # 查看所有容器(包括未运行)的容器状态
    docker inspect -f '{{.State.Status}}' $(docker ps -aq)
    docker inspect --format='{{.State.Status}}' $(docker ps -aq)
    # 可以通过级联调用直接读取子对象 State 的 Status 属性,以获取容器的状态信息:
    docker inspect --format '{{/*读取容器状态*/}}{{.State.Status}}' $INSTANCE_ID
    • 大括号内处理模版指令,大括号外的任何字符都将直接输出
    • .:当前上下文
    • 可以使用 $ 来获取根上下文
top

查看容器中运行的进程信息,支持 ps 命令参数。主要用于容器内没有shell或top命令的

1
docker top [OPTIONS] CONTAINER [ps OPTIONS]
attach

连接到正在运行中的容器,可以同时连接一个容器来共享shell输出

1
docker attach [OPTIONS] CONTAINER
  • --sig-proxy=false:确保CTRL-DCTRL-C不会关闭容器,仅仅用作退出该连接(即detach)
events

从服务器获取实时事件

1
docker events [OPTIONS]
  • -f:根据条件过滤事件
  • 时间设置(如果指定的时间是到级的,需要将时间转成时间戳。如果时间为日期的话,可以直接使用,如--since="2016-07-01"
    • --since:从指定的时间戳后显示所有事件
    • --until:流水时间显示到指定的时间为止
logs

获取容器的日志

1
docker logs [OPTIONS] CONTAINER
  • -f:跟踪日志输出
  • --tail:仅列出最新N条容器日志
  • -t:显示时间戳
  • --since:显示某个开始时间的所有日志
wait

阻塞运行直到容器停止,然后打印出它的退出代码

1
docker wait [OPTIONS] CONTAINER [CONTAINER...]
export

容器文件系统作为一个tar归档文件导出,主要用来制作基础镜像(导入命令[docker import](# import))

1
docker export [OPTIONS] CONTAINER
  • -o:设置导出的文件
port

列出指定的容器的端口映射情况

1
docker port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]]

images管理

images

列出本地镜像

1
docker images [OPTIONS] [REPOSITORY[:TAG]]
  • -a:列出本地所有的镜像(含中间映像层,默认情况下会过滤掉中间映像层)
  • --digests:显示镜像的摘要信息
  • -f:显示满足条件的镜像
  • --format:指定返回值的模板文件
  • --no-trunc:显示完整的镜像信息(防止信息显示不全)
  • -q:静默模式,只显示镜像ID
rmi

删除本地一个或多少镜像

1
docker rmi [OPTIONS] IMAGE [IMAGE...]
  • -f:强制删除
tag

标记本地镜像,将其归入某一仓库

1
docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
build

通过 Dockerfile 创建镜像

1
docker build [OPTIONS] PATH | URL | -
  • 资源限制
    • --cpu-shares:设置 cpu 使用权重
    • --cpuset-cpus:指定使用的CPU id
    • -m:设置内存最大值
    • --memory-swap:设置swap的最大值,"-1"表示不限swap
  • --disable-content-trust:忽略校验,默认开启
  • -f:指定要使用的Dockerfile路径,默认为当前目录
  • --force-rm,--rm:设置镜像过程中删除中间容器
  • --quiet,-q:安静模式,成功后只输出镜像 ID
  • --squash:将 Dockerfile 中所有的操作压缩为一层
  • --tag,-t:镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签

例子:

1
2
3
4
5
6
# 使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1
docker build -t runoob/ubuntu:v1 .
# 使用URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像
docker build github.com/creack/docker-firefox
# 通过 -f Dockerfile 设置dockerfile文件的位置
docker build -f /path/to/a/Dockerfile .
history

查看指定镜像的创建历史

1
docker history [OPTIONS] IMAGE
  • --no-trunc:显示完整的提交记录(防止信息显示不全)
  • -q:安静模式,仅列出提交记录ID
save

将指定镜像保存成 tar 归档文件(导入命令[docker load](# load))

1
docker save [OPTIONS] IMAGE [IMAGE...]
  • -o:设置导出的文件
load

导入使用 [docker save](# save) 命令导出的镜像

1
docker load [OPTIONS]
  • --input,-i:指定导入的文件,代替 STDIN(默认为标准输入)

    1
    2
    docker load < busybox.tar.gz
    docker load --input fedora.tar
  • --quiet,-q:精简输出信息

import

从 [docker export](# export) 创建的归档文件中创建镜像

1
docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
  • -c:应用 [docker export](# export) 指令创建镜像(默认为标准输入)
  • -m:提交时的说明文字
导入导出汇总

一般来说,分为以下两种,不能混用:

  • 导入/导出image:[docker save](# save)、[docker load](# load)
    • 保存完整记录,但是体积较大
  • 导入/导出快照:[docker export](# export)、[docker import](# import)
    • 没有 layer 信息,dockerfile 里的 workdir,entrypoint 之类的所有东西都会丢失,commit 也会丢失。即丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),体积较小

其区别如下:

  • [docker save](# save) 保存的是镜像(image),[docker export](# export) 保存的是容器(container);
  • [docker load](# load) 用来载入镜像包,[docker import](# import) 用来载入容器包,但两者都会恢复为镜像;
  • [docker load](# load) 不能对载入的镜像重命名,而 [docker import](# import) 可以为镜像指定新名称。

仓库管理

login/logout
1
2
docker login [OPTIONS] [SERVER]
docker logout [OPTIONS] [SERVER]
  • -u:登陆的用户名
  • -p:登陆的密码
pull

从镜像仓库中拉取或者更新指定镜像

1
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
  • -a:拉取所有 tagged 镜像
push

将本地的镜像上传到镜像仓库,需要先登陆到镜像仓库

1
docker push [OPTIONS] NAME[:TAG]

从Docker Hub查找镜像

1
docker search [OPTIONS] TERM
  • --automated:只列出自动构建类型的镜像

  • --no-trunc:显示完整的镜像描述

  • -f <过滤条件>:列出收藏数不小于指定值的镜像

    1
    2
    # 查找所有镜像名包含 java,并且收藏数大于 10 的镜像
    docker search -f stars=10 java

容器文件系统管理

commit

从容器创建一个新的镜像

1
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
  • -a:提交的镜像作者
  • -c:使用Dockerfile指令来创建镜像
  • -m:提交时的说明文字
  • -p:在commit时,将容器暂停
cp

容器与主机之间的数据拷贝

1
2
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
  • -L:保持源目标中的链接
diff

检查容器里文件结构的更改

1
docker diff [OPTIONS] CONTAINER

三、docker网络

docker网络分类:

  • 单机网络
    • Bridge Network
    • Host Network
    • None Network
  • 多机网络
    • Overlay Network

基本概念

整个网络的数据传输是通过数据包的形式来传输的

数据包的打包有一个分层的概念,即ISO/OSI的七层模型和TCP/IP的五层模型:

网络分层

数据包到达另一台电脑或服务器,需要通过路由

IP地址:设备的标识

私有IP地址访问互联网,需要网络地址转换,即NAT

网络地址转换

Linux命令:

  • ping检查ip的可达性,ping不通不能保证机器出问题了,可能是中间路由或者防火墙等其他某一环节出问题了
  • telnet检查服务的可用性

Linux中network namespace

每创建一个container,会同时创建一个network namespace

1
2
3
4
5
6
7
8
9
10
#查看本机network namespace
sudo ip netns list
#删除本机network namespace
sudo ip netns delete xxxx
#添加network namespace
sudo ip netns add xxxx
#在xxxx这个network namespace中执行命令
sudo ip netns exec xxxx ip a
sudo ip netns exec xxxx ip link
sudo ip netns exec xxxx ip link set dev lo up#将xxxx中的lo网卡up

其他相关命令和知识可以参考这篇博客,如链接消失,可见我博客中的备份

Docker Bridge0详解

即bridge Network。

建议参考上一小节中提到的博客

1
2
3
4
5
6
#列出当前docker中有哪些网络
sudo docker network ls
#列出bridge的详细情况,通过Containers一栏可以看出哪个container连接上该bridge
sudo docker network inspect bridge
#列出本机上bridge的相关信息,可以通过该命令看出docker0网卡接上了那个veth接口
brctl show

两个容器之间相互通信的情况:

image-20200210150411463

  • 蓝色框框为两个容器
  • 绿色的一对为veth(虚拟网络接口)
  • docker容器中会连接到主机的docker0网卡上

单个容器访问internet的情况:

image-20200210150701170

  • 主机通过eth0访问外网
  • docker0中的数据通过NAT(网络地址转换)转换到eth0中
  • 这里的NAT是通过iptables实现

即通过link指令,可以将一个容器只需指定对方容器名字就可以链接到另一个容器上,而无需指定IP地址及端口。注意,link指令含有方向,只能单向链接

该指令实际使用不多

直接上例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#创建busybox容器test1
sudo docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"
#创建busybox容器test2,并连接到test1
sudo docker run -d --name test2 --link test1 busybox /bin/sh -c "while true; do sleep 3600; done"
#进入test2中
sudo docker exec -it test2 /bin/sh
#在test2中ping test1(ip:172.17.0.3) 无论名字还是ip均可
ping 172.17.0.3
ping test1
#反之,进入test1
sudo docker exec -it test1 /bin/sh
#在test1中ping test2(ip:172.17.0.2) 只有ip可以
ping 172.17.0.2
ping test2#ping不通
ping 172.17.0.3
ping test1

创建一个新的Bridge并连接上Container:

1
2
3
4
5
6
7
8
9
10
11
12
13
#创建一个新的bridge,叫my-bridge(-d:driver)
sudo docker nerwork create -d bridge my-bridge
#查看bridge
sudo docker network ls
brctl show#和上个命令一样
#创建busybox容器test3,指定network为my-bridge
sudo docker run -d --name test3 --network my-bridge busybox /bin/sh -c "while true; do sleep 3600; done"
#显示bridge及连接的情况
brctl show
#显示bridge的详细信息
sudo docker network inspect bridge的id(通过sudo docker network ls和brctl show可以查看)
#将其他container连接到新建的bridge上
sudo docker network connect my-bridge test2

多个容器若都连接在了同一个用户自己创建的bridge上,则可以不需通过链接就能够通过名字直接访问

1
2
3
4
#进入my-bridge绑定的test3中,可以直接通过名字ping通后来接入的test2(ip:192.18.0.3)
sudo docker rxrc -it test3 /bin/sh
ping 192.18.0.3
ping test2

端口映射

如果想要将docker中的端口转发给外部:

1
2
3
4
5
6
#将容器中的80端口映射到本地的80端口
sudo docker run --name web -d -p 80:80 nginx
#查看映射的端口
sudo docker ps
#此时可以通过curl在外部(虚拟机上)直接访问容器内部的端口
curl 192.168.205.10

示意图:

image-20200211000724333

  • 注:eth1到本地网卡上的端口转发是通过设置虚拟机修改而成的

host和none网络

none network:

1
2
3
4
5
6
7
8
#创建busybox容器test1,指定network为none
sudo docker run -d --name test1 --network none busybox /bin/sh -c "while true; do sleep 3600; done"
#查看none上连接的container
sudo docker network inspect none
#进入container中
sudo docker exec -it test1 /bin/sh
#运行命令
ip a#发现只有lo
  • none network表示一种孤立的网络,除了sudo docker exec -it命令能够访问之外,不能通过其他方法访问

host network:

1
2
3
4
5
6
7
8
#创建busybox容器test1,指定network为host
sudo docker run -d --name test1 --network host busybox /bin/sh -c "while true; do sleep 3600; done"
#查看host上连接的container
sudo docker network inspect host
#进入container中
sudo docker exec -it test1 /bin/sh
#运行命令
ip a#发现此处的网卡和Linux主机上的一样
  • host network表示该容器没有自己独立的namespace,根Linux主机中的namespace network共享同一套
  • 可能会和Linux主机上的一些端口存在冲突

多机通信

为实现如下效果:

多机通信

  • 两个docker的ip地址需要不一样
  • 运用VXLAN技术
  • 通过overlay network实现,创建overlay与bridge、host、none一致
  • 需要用到分布式存储技术(采用etcd),用于在单机上占据ip地址

四、Docker的持久化存储和数据共享

回顾之前讲的Container,Container具有可读可写的能力,image只具有可读的能力。但是在stop再rm Container之后,Container中的数据不会保存,Container中的数据相当于一个临时的数据

Container

docker持久化数据的方案分以下两种:

  • 基于本地文件系统的 Volume.可以在执行 Docker create或 Docker run时,通过-v参数将主机的目录作为容器的数据卷.这部分功能便是基于本地文件系统的 volume管理.
  • 基于plugin的 Volume,支持第三方的存储方案,比如NAS,aws

Volume的类型:

  • 受管理的 data Volume,由 docker后台自动创建.
  • 绑定挂载的 Volume,具体挂载位置可以由用户指定.

Data Volume

场景:一般来说有些容器自己会产生一些数据,我们需要保证这些数据的安全,不想随着container的消失而消失,比如说数据库等,通过Dockerfile中的VOLUME关键字实现。

1
2
3
4
5
6
7
8
#创建mysql container(mysql的Dockerfile中有:VOLUME /var/lib/mysql),初始密码为空,将在/var/lib/mysql处产生的volume重命名为mysql(将mysql volume映射到container中的/var/lib/mysql目录)
sudo docker run -d -v mysql:/var/lib/mysql --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
sudo docker ps
#查看volume
sudo docker volume ls
sudo docker volume 上一步命令查看的id #会自动挂载到/var/lib/docker/volumes/xxxx(id)/_data
#删除volume
sudo docker volume rm id或者-v指定的名字

Bind Mounting(挂载宿主机目录)

bind Mounting不需要在Dockerfile中定义文件产生的路径,只需在命令行中指定即可:

1
2
#创建容器,将当前目录$(pwd)映射到container中的/usr/share/nginx/html目录
sudo docker run -d -v $(pwd):/usr/share/nginx/html id

五、Docker Compose多容器部署

官方链接:https://docs.docker.com/compose/https://docs.docker.com/compose/compose-file/

多容器的app会带来的缺点:

  • 要从 Dockerfile build image或者 Dockerhub拉取 image
  • 要创建多个 container
  • 要管理这些 container(启动、停止、删除)

Docker Compose的诞生就是为了解决该问题,可以看成是批处理

  • Docker Compose是一个命令行工具
  • 这个工具可以通过一个yml文件定义多容器的 docker应用
  • 通过一条命令就可以根据yml文件的定义去创建或者管理这多个容器

yml文件默认名:docker-compose.yml,包含三个重要概念:Services、Networks、Volumes

Services:

  • 一个 service代表一个 container,这个 container可以从dockerhub的image来创建,或者从本地的 Dockerfile build出来的image来创建

  • Service的启动类似 docker run,我们可以给其指定network和 volume,所以可以给 service指定 network和Volume的引用(service中的参数和docker run中的参数类似)

    1
    2
    3
    4
    5
    6
    7
    services:
    db:#services名字
    image:postgres:9.4#采用的image
    volumes:
    - “db-data:/var/lib/postgresql/data”
    network:#新建的bridge
    - back-tier
    1
    2
    3
    4
    5
    6
    7
    8
    services:
    worker:
    build:./worker
    links:#有了network,links可有可无
    - db
    - redis
    network:
    - back-tier

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: '3'

services:

wordpress:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- my-bridge

mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql
networks:
- my-bridge

volumes:
mysql-data:

networks:
my-bridge:
driver: bridge

安装和基本使用

安装:

  • docker for mac或windows 会自动安装

  • Linux安装步骤:https://docs.docker.com/compose/install/

    1
    2
    3
    4
    5
    6
    #下载至/usr/local/bin/docker-compose
    sudo curl -L "https://github.com/docker/compose/releases/download/1.25.3/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的使用大部分都会联合yml文件的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建docker-compose.yml文件中的service
docker-compose up
docker-compose up -d#后台运行,但是不会打印log

#查看docker-compose中的情况
docker-compose ps
docker-compose images#列出所定义的container以及使用的image
#执行container中的命令
docker-compose exec yml文件中定义的service container中的命令

#停止docker-compose中的容器
docker-compose stop
#停止并删除docker-compose中的容器、bridge、volume
docker-compose down

水平扩展和负载均衡

通过docker-compose命令的--scale参数

1
2
#多开service中的container
docker-compose up --sacle yml文件service中的某一个container=个数

六、容器编排Swarm mode

Swarm是一种集群的架构,其中含有节点,每一个节点中可以含有两种角色,即manager和worker

  • manager:整个集群的大脑,至少需要2个及以上,所以需要同步,docker提供了一个内置的分布式的存储数据库,通过raft协议进行同步
  • worker:节点比manager多,通过gossip网络进行同步

底层架构

重要概念:

  • Service:与docker Compose中的service类似,但是最终运行在那台机器上是不确定的
  • Replicas:横向扩展时,一个replicas就是一个容器

Service和Replicas

注:此图产生了三个容器,会调度系统调度到不同的节点上去,即该service最终会运行在那些swarm节点上是不知道的

swarm上创建service时的调度过程

例子:

创建一个三节点的swarm集群:

节点一:

1
2
3
4
#初始化swarm并创建manager节点,同时指定manager的地址
sudo docker swarm init --advertise-addr=192.168.205.10
#查看swarm当前的节点
sudo docker node ls

借点二:

1
2
#创建worker节点并加入该swarm集群(init后会提供该命令),用于将另一台机器加入称为worker
sudo docker join --token xxxx(init后会提供) 192.168.205.10:端口号

节点三:

1
2
#创建worker节点并加入该swarm集群(init后会提供该命令),用于将另一台机器加入称为worker
sudo docker join --token xxxx(init后会提供) 192.168.205.10:端口号

Service的创建维护和水平扩展

docker service命令类似于docker run,只是不是在本地上运行

1
2
3
4
5
6
7
8
9
#创建一个busybox的service,叫demo
sudo docker service create --name demo busybox sh -c "while true; do sleep 3600;done"
#查看service
sudo docker service ls
sudo docker service ps service名字#查看service的详细情况(包括分布在那台机器/节点上)
#横向扩展service
sudo docker service scale service名字=扩展数#通过ls命令显示的REPLICAS栏可以查看
#删除service
sudo docker service rm demo#由于可能存在横向扩展,所以实际上会比较慢
  • sudo docker service ls命令显示的REPLICAS栏:
    • 分母:创建时规定的横向扩展数量(scale)
    • 分子:有几个已经ready了
  • scale命令扩展时,如果有部分节点上的service失效了(退出、shutdown等),系统会在任意节点上再起一个直到达到scale规定的数目

集群服务器通信——RoutingMesh

RoutingMesh的两种体现:

  • Internal:Container和Container之间的访问通过overlay网络(通过VIP虚拟IP)
  • Ingress:如果服务有绑定接口,则此服务可以通过任意swarm节点的相应接口访问

Internal

docker Compose在单机的情况下,不同的service可以通过对方的名字相互访问(底层通过DNS服务实现)。而在swarm class中,不同的service有可能在不同的节点上,不同的service之间也能通过service name通信,也有DNS服务。

对于Swarm来说,有内置的DNS服务发现的功能,通过service命令创建service时,如果是连接到一个overlay的网络上,会为连接到overlay网络上的所有的service去增加一条DNS的记录,通过该记录就能知道IP地址(并不是实际上该service所在的容器的ip地址,而是虚拟的ip地址,即VIP,一旦service创建好后,VIP就不会改变,但是和具体的ip地址绑定是通过LVS(Linux Virtual Service)实现的),就可以访问其服务

例子:

swarm-manager节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#创建overlay网络,命名demo
sudo docker network create -d overlay demo
#创建一个whoami的service,提供web服务,如果访问8000端口会返回他的hostname
sudo docker service create --name whoami -p 8000:8000 --network demo -d jwilder/whoami#将内部8000端口映射到本地8000端口
#查看是否运行
sudo docker service ls
#查看运行在哪里
sudo docker service ps whoami#swarm-manager
#查看容器 是否映射出了8000
sudo docker ps
#查看8000端口
curl 127.0.0.1:8000


#创建第二个service,采用busybox,命名client
sudo docker service create --name client -d --network demo busybox sh -c "while true; do sleep 3600; done"
#查看是否运行
sudo docker service ls
#查看运行在哪里
sudo docker service ps client#swarm-worker1
#查看容器 是否映射出了8000
sudo docker ps


#扩展下whoami
sudo docker service scale whoami=2
#查看另一台在哪里
sudo docker service ps whoami#swarm-manager、swarm-worker2

进入swarm-worker1节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#尚未扩展前:
#查看service是否运行
sudo docker ps
#进入service中
sudo docker exec -it service的id sh
#直接ping另一台service
ping whoami#能够ping通,能够发现实际上是ping10.0.0.7

#横向扩展后:
ping whoami#能够ping通,能够发现实际上是ping10.0.0.7

#直接去查看swarm class中维护的dns服务器
nslookup whoami
#查看真正的容器地址
nslookup tasks.whoami
  • 不管service有几个横向扩展,但是最后的VIP只有一个,而且访问这个VIP时,Swarm会自动做负载均衡,依次轮流访问这几个横向扩展的service
  • 上述的负载均衡以及VIP均通过LVS(Linux Virtual Service)实现

Ingress

  • 外部访问的负载均衡
  • 服务端口被暴露到各个 swarm节点
  • 内部通过IPVS进行负载均衡

当我们去任何一台Swarm节点上去访问端口服务的时候,会把该服务通过本地节点的IPVS(IP的virtual service),通过LVS把该服务负载均衡:

示意图

  • 当外部访问docker host3的8080端口时(该节点上没有对应service),IPVS会将该请求转发到另外两台具有service的节点上

例子:

swarm-manager节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
#创建overlay网络,命名demo
sudo docker network create -d overlay demo
#创建一个whoami的service,提供web服务,如果访问8000端口会返回他的hostname
sudo docker service create --name whoami -p 8000:8000 --network demo -d jwilder/whoami#将内部8000端口映射到本地8000端口
#扩展下whoami
sudo docker service scale whoami=2
#查看运行情况
suod docker service ps whoami#swarm-manager、swarm-worker2
#不断获取,会依次返回两台不同节点机器的id(负载均衡)
curl 127.0.0.1:8000
curl 127.0.0.1:8000
curl 127.0.0.1:8000
...

进入swarm-worker1节点:

1
2
3
4
5
#获取,发现任可以访问
curl 127.0.0.1:8000

#查看本地转发规则
iptables -nl -t nat

docker stack部署swarm

利用Docker compose file,即yml文件,主要通过deploy命令,官方文档:https://docs.docker.com/compose/compose-file/#deploy

注意:该处的docker compose file中不能利用build命令构建本地image,只能通过远程拉取

以下均为deploy下的子命令:

  • endpoint_mode
    • vip(默认):service之间通过vip互访(底层会通过LVS自动做均衡负载)
    • dnsrr:直接使用service的ip地址互访,也会通过dnsrr(DNS round-robin)做均衡负载
  • labels:帮助信息
  • mode
    • global:不能通过scale命令做横向扩展,整个class中只有一个service
    • replicated(默认):可以横向扩展
  • placement
  • replicas
    • mode设置成replicated时,可以在初始化时就指定需要几个service
  • resources:资源的限制
    • cpu:‘0.5’
    • memory:20M
  • restart_policy:重启条件即参数设置
    • delay:延时
    • max_attempts:最大尝试次数
  • update_config:更新时要遵循的原则
    • parallelism:并行数(最多能够同时更新几个service)
    • delay:延时(每次更新的间隔时间)

例子:

利用yml文件部署wordpress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
version: '3'

services:

web:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s

mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql
networks:
- my-network
deploy:
mode: global
placement:
constraints:
- node.role == manager

volumes:
mysql-data:

networks:
my-network:
driver: overlay

进入swarm manager节点:

1
2
3
4
5
6
7
8
9
10
#语法:sudo docker stack deploy 自定义的名字 --compose-file=xxxx.yml
sudo docker stack deploy wordpress --compose-file=docker-compose.yml

#查看情况
sudo docker stack ps wordpress
#只查看service
sudo docker stack services wordpress

#删除所有container及网络
sudo docker stack rm wordpress

密码管理

docker compose file中有些是关于数据库的用户名和密码的,为了安全性考虑需要secret manager

需要加密的部分:

  • 用户名密码
  • SSH Key
  • TLS认证
  • 任何不想让别人看到的数据

docker secret management特点:

  • 存在 Swarm Manager节点 Raft database里(加密的).
    • 多个manager节点通过Raft database确保数据一致
  • Secret可以 assign给一个 service,这个 service就能看到这个 secret
  • 在 container内部 Secret看起来像文件,但是实际是在内存中

使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#创建secret
#方法一:
#从文件中创建secret,创建完成后最好删除掉该文件
sudo docker secret create secret的名字 要加密的文件
#查看
sudo docker secret ls
#方法二
#从标准输入中创建secret
echo "admin" | sudo docker secret create secret的名字 -
#查看
sudo docker secret ls

#删除secret
sudo docker secret rm secret的名字

#使用secret
#创建一个busybox的container,命名client(可以通过多次使用--secret传入几个secret)
sudo docker service create --name client --secret secret的名字 busybox sh -c "while true; do sleep 3600; done"
#进入container
sudo docker exec -it 容器的id(可通过docker ps查看) sh
cd /run/secrets#会存在一个secret名字的文件
#可以直接查看secret的明文
cat secret名字的文件

最典型使用mysql时传入密码的例子:

1
2
3
4
5
6
echo "admin" | sudo docker secret create my-pw -
#通过MYSQL中的MYSQL_ROOT_PASSWORD_FILE环境变量指定密码
sudo docker service create --name db --secret my-pw -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/my-pw mysql
#进入container内部确认mysql密码
sudo docker exec -it 容器的id(可通过docker ps查看) sh
mysql -u root -p#之后输入密码:admin即可进入mysql的交互中

docker stack中使用secret

通过secret指定使用哪个secret

例子:

利用yml文件部署wordpress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
version: '3'

services:

web:
image: wordpress
ports:
- 8080:80
secrets:
- my-pw
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/my-pw
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s

mysql:
image: mysql
secrets:
- my-pw
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my-pw
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql
networks:
- my-network
deploy:
mode: global
placement:
constraints:
- node.role == manager

volumes:
mysql-data:

networks:
my-network:
driver: overlay

#
# secrets:
# my-pw:
# file: ./password

service更新

注意:为保证service不会中断,需要保证scale>=2

对正在运行的service进行更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#创建overlay网络,命名demo
sudo docker network create -d overlay demo
#创建一个whoami的service,提供web服务,将本地的5000映射到class中的8080端口
sudo docker service create --name web -p 8080:5000 --network demo xiaopeng163/python-flask-demo:1.0
#查看
sudo docker service ps web
#扩展下web
sudo docker service scale web=2
#查看运行情况
curl 127.0.0.1:8080


#更新image
sudo docker service update --image xiaopeng163/python-flask-demo:2.0 web
#再次查看,会发现1.0的已经被shutdown了,并重新开了两个2.0的container
sudo docker service ps web


#更新端口
sudo service updata --publish-rm 8080:5000 --publish-add 8088:5000 web

七、容器编排Kubernetes

kubernetes架构:

架构图

其中Master节点架构:

Master节点架构

  • API Service:暴露给外界访问
  • Scheduler:调度模块
  • Controller:控制模块,对节点的控制
  • etcd:分布式的存储,存储k8s的状态和配置

node节点架构:

node节点架构

  • Pod:在k8s中,是在容器中调度的最小单位,是具有相同的namespace(包含了所有namespace,如user namespace、network namespace)的一些Container组合
  • Docker:容器技术之一(k8s中采用docker,还有其他容器技术)
  • kubelet:类似于一个代理,受master节点控制,负责在node节点上创建、管理容器、network以及volume
  • kube-proxy:端口的代理转发以及service的服务发现和负载均衡
  • Flientd:日志的采集、存储、查询
  • Optional Add-ons:插件

搭建k8s单节点环境

k8s首席构造师github:https://github.com/kelseyhightower

常用工具介绍:

  • kubernetes-the-hard-way:不借助任何脚本,从命令行去操作安装kubernetes
  • minikube:本地快速创建一个节点的kubernetes集群
    • 通过利用virtualbox创建一台虚拟机,该虚拟机中会安装好kubernetes
  • kubeadm:方便的本地搭建多节点的kubernetes集群
  • kops:在云上搭建kubernetes集群
  • tectonic:少于10个节点免费
  • play with kubernetes:网站上搭建,无需任何工作,四个小时保存时间

minikube安装(参考文档):

  • ubuntu上:

    1
    2
    3
    4
    5
    6
    7
    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_1.7.2-0_amd64.deb \
    && sudo dpkg -i minikube_1.7.2-0_amd64.deb
    #使用以下命令检查下
    egrep -q 'vmx|svm' /proc/cpuinfo && echo yes || echo no
    #如果输出 no 且如本文一样使用Windows + VirtualBox + minikube,需要通过以下命令创建一个单节点的kubernetes集群(具体命令后文会细说)
    sudo minikube start --vm-driver=none#
    sudo minikube config set vm-driver none#
  • 需要安装依赖的kubectl(客户端的CLI,可看上面的架构图)

    1
    2
    3
    4
    5
    6
    #ubuntu上可以使用该命令安装:
    sudo snap install kubectl --classic
    #其余Linux发行版可以使用以下命令
    curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
    chmod +x ./kubectl
    sudo mv ./kubectl /usr/local/bin/kubectl
  • 虚拟化工具virtualbox

    1
    2
    3
    #测试版本号
    minikube version
    kubectl version

kubectl的上下文:一些配置信息或者认证信息,称之为context,即上下文

kubectl的官方帮助文档

命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#创建一个单节点的kubernetes集群(适用于mac或Linux上直接装minikube+virtualbox用户)
sudo minikube start --vm-driver=virtualbox
#适用于本文中Windows+virtualbox,在virtualbox中的虚拟机中安装minikube用户,缺点是有可能会影响Linux虚拟机和不能使用minikube ssh等命令
sudo minikube start --vm-driver=none
#如果是国内,可以尝试如下命令
sudo minikube start --vm-driver=none --image-mirror-country=cn
sudo minikube start --vm-driver=none --image-mirror-country=cn --registry-mirror=https://registry.docker-cn.com --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers
#进入通过minikube创建的虚拟机中
sudo minikube ssh
#停止虚拟机
sudo minikube stop

#当前使用的config的基本情况
sudo kubectl config view
#查看当前使用的上下文
sudo kubectl config get-contexts
#当前的kubernetes的集群情况
sudo kubectl cluster-info

kubectl命令行补全补丁

kubectl自带命令行补全的脚本,通过命令:

1
2
3
#Linux
kubectl completion bash
source <(kubectl completion bash)

进行打补丁补全。其他系统可以参考官方文档最后一节Optional kubectl configurations

最小调度单位pod

Pod是k8s集群中运行部署应用或服务的最小单元,一个Pod由一个或多个容器组成,kubernetes中不对容器进行直接操作。在一个Pod中,容器共享网络和存储,并且在一个Node上运行。

Kubernetes为每个Pod都分配了唯一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。Kubernetes要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,这通常采用虚拟二层网络技术来实现,例如Flannel、Open vSwitch等。因此,在Kubernetes里,一个Pod里的容器与另外主机上的Pod容器能够直接通信。

pod具有如下的一些特性:

  • pod共享一个namespace(包含用户、网络、存储等)
    • 如果一个pod中有两个Container,则这两个Container可以直接通过localhost进行通信,如同在本地一个Linux上运行的两个进程

基本操作例子:

pod_nginx.yml文件(根据kubernetes提供的一个API格式定义一个资源,这里是pod):

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod #资源类型
metadata:
name: nginx #pod名字
labels:
app: nginx
spec: #pod中的关键部分
containers: #可以包含多个容器
- name: nginx #第一个Container Container名字
image: nginx
ports:
- containerPort: 80

创建及基本管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#查看kubernetes集群是否正常
kubectl version

#根据pod_nginx.yml创建pod
kubectl create -f pod_nginx.yml
#kubectl delete -f pod_nginx.yml#删除pod
#查看pod
kubectl get pods
#显示pod的详细信息(包括容器的ip(172.17.0.4)、在那个节点上)
kubectl get pods -o wide

#进入minikube中的虚拟机中
minikube ssh
#查看虚拟机中container
docker ps
#进入容器内部
docker exec -it 容器id sh
#退出容器
exit
#查看bridge网络的详细情况 可以找到创建的iginx连接到了bridge网络上(ip:172.17.0.4/16)
docker network inspect bridge
exit#退出minikube

#直接对pod进行可交互的操作,进入pod中的第一个容器内部(可以通过-c指定进入那个容器内部,默认第一个) 执行sh
kubectl exec -it nginx(pod名字) sh
#具体描述一个pod
kubectl desrcibe pods nginx(pod名字)

#将minikube中的80端口暴露出来为8080端口
kubectl port-forward nginx8080:80#一旦停止,端口映射就会停止

横向扩展

ReplicationController种类:

rc_nginx.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 3 #3个横向扩展
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
  • 通过ReplicationController创建的pod,kubernetes能够自动帮我们维持对应数量的pods

创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#根据rc_nginx.yml创建pod
kubectl create -f rc_nginx.yml

#查看 ReplicationController 情况
kubectl get rc
#查看pod(可以发现有3个pod)
kubectl get pods

#尝试删除一个pod
kubectl delete pods pod的id(通过get pods查询)
#再次查看pod(发现仍有3个pod)
kubectl get pods


#横向扩展 修改为2
kubectl scale rc nginx(名字) --replicas=2
kubectl get pods#查看后只有两个
kubectl get rc

ReplicaSet种类:

rs_nginx.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx
labels:
tier: frontend
spec:
replicas: 3 #3个横向扩展
selector:
matchLabels:
tier: frontend
template:
metadata:
name: nginx
labels:
tier: frontend
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
  • ReplicaSet是ReplicationController的升级版,ReplicaSet支持new set-based selector

创建:

1
2
3
4
5
6
7
8
9
10
11
12
#根据rs_nginx.yml创建pod
kubectl create -f rs_nginx.yml

#查看 ReplicaSet 情况
kubectl get rs
#查看pod(可以发现有3个pod)
kubectl get pods

#横向扩展 修改为2
kubectl scale rs nginx(名字) --replicas=2
kubectl get pods#查看后只有两个
kubectl get rs

Deployments

官方文档:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

Deployments控制器提供了Pod和ReplicaSets的声明性更新。即Deployments会描述一种希望的状态,如有三个扩展(–replicas=3)、pod中的具体的docker image版本以及相关的更新,Deployments controller都会努力使该声明实现(Deployments会通过创建ReplicaSet进而来创建pods)

注意:不能独立对 通过Deployments创建的Pod和ReplicaSets 进行操作(尤其是删除操作)

创建和管理

deployment_nginx.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment #类型为Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3 #此处其实就是ReplicaSet 保证pod的数量
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec: #指定pod需要的Container
containers:
- name: nginx #Container名字
image: nginx:1.12.2 #Container image
ports:
- containerPort: 80

创建:

1
2
3
4
5
6
7
8
9
#根据deployment_nginx.yml创建Deployment
kubectl create -f deployment_nginx.yml

#查看Deployment
kubectl get deployment
#查看 ReplicaSet 情况
kubectl get rs
#查看 pod 情况
kubectl get pods

Deployment相关命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#查看Deployment
kubectl get deployment
#查看Deployment 显示更多信息
kubectl get deployment -o wide

#对deployment中的image进行升级
kubectl set image deployment(resource) nginx-deployment(名字) nginx=nginx:1.13(image)
#查看Deployment 显示更多信息(包括image版本)
kubectl get deployment -o wide
#查看 ReplicaSet 情况
kubectl get rs#会发现存在一个旧的(停止)一个新的
#查看 pod 情况
kubectl get pods

#查看整个 deployment 的历史
kubectl rollout history deployment(resource) nginx-deployment(名字)
#退回 deployment 的之前版本(即没有更新image之前)
kubectl rollout undo deployment(resource) nginx-deployment(名字)
#查看Deployment 显示更多信息(包括image版本)
kubectl get deployment -o wide

#查看节点信息(ip地址)
kubectl get node -o wide

使用Tectonic搭建本地多节点

Tectonic为CoreOS的产品:收费企业级产品,可免费试用。

搭建本地多节点教程:https://coreos.com/tectonic/docs/latest/tutorials/sandbox/install.html(实测Tectonic下的sandbox已经不能正常获取!!!该实验无法正常进行!!!)

本质是通过vagrant在virtualbox上创建多台虚拟机(CoreOS系统,Container Linux)

修改kubeconfig文件,使kubectl命令同时支持minikube和tectnic(修改kubeconfig文件,添加新的cluster、context、user,具体配置可以参看官方说明),通过以下命令可以进行切换:

1
2
3
4
#查看目前的上下文变量
kubectl config get-contexts
#切换
kubectl config use-context 上一个命令中的NAME栏名字

基础网络Cluster Network

Cluster Network的官方说明

pod_busybox.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: busybox-pod
labels:
app: busybox
spec:
containers:
- name: busybox-container
image: busybox
command:
- sleep
- "360000"

pod_nginx.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx
ports:
- name: nginx-port
containerPort: 80

service_nginx.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 8080
nodePort: 8080
targetPort: nginx-port
protocol: TCP
selector:
app: nginx
type: NodePort

w1节点上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#根据.yml创建pod
kubectl create -f pod_busybox.yml
kubectl create -f pod_nginx.yml

#查看具体情况
kubectl get pods -o wide
#两个pod详细情况:
#busybox-pod ip:10.2.0.50 节点w1
#nginx-pod ip:10.2.0.51 节点w1

#在w1中(未进入pod中)ping
ping 10.2.0.50#能够ping通
ping 10.2.0.51#能够ping通

#进入busybox-pod
kubectl exec -it busybox-pod sh
#查看ip
ip a
#尝试ping nginx-pod
ping 10.2.0.51#能够ping通

c1节点:

1
2
3
4
#c1节点ping w1节点上的busybox-pod
ping 10.2.0.50#能够ping通
#c1节点ping w1节点上的nginx-pod
ping 10.2.0.51#能够ping通

上述网络关系的拓扑图:

网络关系拓扑图

  • flannel插件:coreos出品,开源免费,通过该插件实现了两台机器之间的overlay网络
  • 诸如flannel插件的其他开源插件还有很多,在本节开头的网址进入后会有其他插件介绍说明以及插件必须遵守的原则

Service

在cluster中,每个pod都有自己独立的ip地址,并且都能够相互ping通,不论pod是否在同一台设备(节点)上(机器本身也能够ping通任意一个pod的ip地址,详见上一节)

注意:不要直接去使用和管理pods:

  • 当我们使用 ReplicaSet 或者 ReplicationController 水平扩展 scale 的时候,Pods有可能会被 terminated(终止)
  • 当我们使用 Deployment 的时候,我们去更新 Docker Image Version,旧的Pods会被 terminated(终止),然后新的Pods创建

在Kubernetes中,Pod会经历“生老病死”而无法复活,也就是说,分配给Pod的IP会随着Pod的销毁而消失,这就导致一个问题——如果有一组Pod组成一个集群来提供服务,某些Pod提供后端服务API,某些Pod提供前端界面UI,那么该如何保证前端能够稳定地访问这些后端服务呢?这就是Service的由来。

Service在Kubernetes中是一个抽象的概念,它定义了一组逻辑上的Pod和一个访问它们的策略(通常称之为微服务)。这一组Pod能够被Service访问到,通常是通过Label选择器确定的。

创建service:

1、命令行创建

1
2
#给pod创建service 供外部访问
kubectl expoese <resources> <resources名字>

2、yml文件中通过kind: Service定义,主要有以下三种service

  • ClusterIP:ip地址是cluster内部均可以访问的,但是外界缺无法访问(这种内部的ip称为ClusterIP),外界可以通过service的ip访问(一般service的ip不会变动,而pod的ip会变动)
  • NodePort:将要访问的port绑定到所有node上,因为node对外可以提供访问(假设node的ip为公网ip),所以该类型的service外界也是可以访问的,但是这种方式暴露的port有范围限制(30000-32767)
  • LoadBalancer:一般需要结合云服务使用,由云服务商提供。实际环境中运用较多
  • ExternalName:通过DNS的方式。实际环境中运用较多
  • Default:即ClusterIP,默认为ClusterIP方式

以上均是通过ip地址访问的,但是其实也是可以通过dns访问,但是需要相关的插件


ClusterIP的相关例子:

例子1:

w1节点上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#根据上一节.yml创建pod
kubectl create -f pod_busybox.yml
kubectl create -f pod_nginx.yml

#查看具体情况
kubectl get pods -o wide
#两个pod详细情况:
#busybox-pod ip:10.2.0.254 节点w1
#nginx-pod ip:10.2.0.252 节点w1

#创建service
kubectl expoese pods nginx-pod

#查看service状态
kubectl get svc
#service name:nginx-pod ip:10.3.248.3

c1节点:

1
curl 10.3.248.3#可以直接通过service访问pod

例子2:

deployment_python_http.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: service-test
spec:
replicas: 2
selector:
matchLabels:
app: service_test_pod
template:
metadata:
labels:
app: service_test_pod
spec:
containers:
- name: simple-http
image: python:2.7
imagePullPolicy: IfNotPresent
command: ["/bin/bash"]
args: ["-c", "echo \"<p>Hello from $(hostname)</p>\" > index.html; python -m SimpleHTTPServer 8080"]
ports:
- name: http
containerPort: 8080

w1节点上:

1
2
3
4
5
6
7
#docker pull python:2.7
#根据.yml文件创建Deployment
kubectl create -f deployment_python_http.yml

kubectl get pods -o wide
#pod1 name:service-test-1863849916-b13b6 ip:10.2.0.142 节点w1
#pod2 name:service-test-1863849916-ts7xr ip:10.2.0.141 节点w1

c1节点上:

1
2
3
4
curl 10.2.0.142:8080#可以直接访问pod1
<p>Hello from service-test-1863849916-b13b6</p>
curl 10.2.0.141:8080#可以直接访问pod2
<p>Hello from service-test-1863849916-ts7xr</p>

w1节点上:

1
2
3
4
5
6
7
#查看deployment情况
kubectl get deployment
#创建deployment服务service-test
kubectl expose deployment service-test
#查看服务情况
kubectl get svc
#service name: service-test ip:10.3.120.168

c1节点上:

1
2
3
4
5
6
7
8
9
curl 10.3.120.168:8080#可以直接通过service访问pod
<p>Hello from service-test-1863849916-b13b6</p>
curl 10.3.120.168:8080#可以直接通过service访问pod
<p>Hello from service-test-1863849916-ts7xr</p>
curl 10.3.120.168:8080#可以直接通过service访问pod
<p>Hello from service-test-1863849916-ts7xr</p>
curl 10.3.120.168:8080#可以直接通过service访问pod
<p>Hello from service-test-1863849916-b13b6</p>
...
  • 访问service ip会自动做一个负载均衡

例子3:

接例子2中的换件,在c1中:

1
2
#不停地调用
while true; do curl 10.3.120.168:8080; done

w1节点中:

1
2
3
4
5
6
#直接编辑deployment_python_http.yml 达到应用升级
kubectl edit deployment service-test#会自动打开deployment_python_http.yml文件
#将 Hello from $(hostname) 修改为 Hello new version from $(hostname)

#查看pods
kubectl get pods#可以看到旧的pods已经终止,同时会生成并运行一个新的pods
  • 修改deployment_python_http.yml文件后,c1中的程序检测到该服务会停止一段时间,但是最后会恢复至正常
  • 该更新并不是Rolling Updata,即并不是零宕机更新,有一段时间会存在不能访问服务器

NodePort类型service以及Label简单应用

演示:

service中(c1节点pod_nginx.yml):

创建pod_nginx.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx #具体的label,与后文中的service的yml文件中的label对应
spec:
containers:
- name: nginx-container
image: nginx
ports:
- name: nginx-port #端口名字
containerPort: 80 #具体的端口
  • labels:几乎所有的资源都可以设置一个label,由一对key value组成,且可以设置不止一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#创建pod
kubectl create -f pod_nginx.yml
#查看状态
kubectl get pods -o wide

#通过expose给改pod创建一个NodePort类型的service
kubectl expose pods nginx-pod --type=NodePort
#查看状态
kubectl get svc
#name:kubernetes ClusterIP ip:10.3.0.1 port:443/TCP
#name:nginx-pod NodePort ip:10.3.38.138 port:80:31404/TCP
kubectl get node -o wide
#查看c1的ip
kubectl describe node c1的id#172.17.4.101
#查看w1的ip
kubectl describe node w1的id#172.17.4.201

#删除该service,注意pod还在
kubectl delete service nginx-pod
  • 直接在浏览器中打开172.17.4.101:31404172.17.4.201:31404即可正常访问Nginx

NodePort类型的service会将端口映射到整个Cluster上的每个node的ip地址上,所以可以通过Cluster的任意一个节点的ip地址+端口去访问服务

类似创建pod,该service也可以通过.yml文件(service_nginx.yml)创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service #创建service
metadata:
name: nginx-service #名字
spec:
ports:
- port: 32333 #需要暴露的端口
nodePort: 32333 #暴露到nodeport上
targetPort: nginx-port #目标端口名,对应上述pod的yml文件中描述的port
protocol: TCP
selector: #通过label去选择到底暴露那个pod
app: nginx
type: NodePort #创建service的类型
  • 注意selector应该和上文中的pod创建时指名的label一致,指名是该pod

再次创建:

1
2
3
4
5
6
7
8
9
#查看运行中的pod的label是否和yml文件中描述的吻合
kubectl get pods --show-labels

#创建service
kubectl create -f service_nginx.yml
#查看
kubectl get svc
#name:kubernetes ClusterIP ip:10.3.0.1 port:443/TCP
#name:nginx-pod NodePort ip:10.3.124.158 port:32333:32333/TCP
  • 此时,直接在浏览器中打开172.17.4.101:32333172.17.4.201:32333即可正常访问Nginx

label例子演示:

由上文中提到的,几乎所有的资源都可以设置一个label,由一对key value组成,且可以设置不止一个,具体使用如下:

pod_busybox.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: busybox-pod
labels:
app: busybox
spec:
nodeSelector: #定义一个node的selector(筛选器)
hardware: good #自定义的label
containers:
- name: busybox-container
image: busybox
command:
- sleep
- "360000"

创建:

1
2
3
4
#创建pod
kubectl create -f pod_busybox.yml
#查看状态
kubectl get pods#可以发现现在该busybox pod的状态都是pending
  • pod_busybox状态一直是pending的原因为:整个Cluster中的所有node上的label匹配不到hardware=good的label,所以该pod一直不能正常部署到node上
1
2
3
4
5
6
7
8
9
10
#查看所有node上的所有label
kubectl get node --show-labels

#获取node的id(name)
kubectl get node
#手动在node上添加label
kubectl label node node的id hardware=good

#等待一阵之后,查看
kubectl get pods#发现已经由pending状态变为Running状态