1、容器的起源

软件应用通常依赖于运行环境提供的其他库、配置文件或服务。比如在红帽的系统上安装RPM软件包,或者对源码进行编译的过程中,都会检测该软件包对其他库的依赖项。
而容器正是打包应用以简化部署和管理的一种方式。
最早期,容器在Linux上的实现称为LinuX Container,简称LXC。
image.png
本质上是使用Kernel本身提供的功能Namespace进行资源隔离,使用一些lxc命令安装一个相对完整的OS及应用,如:

  1. lxc-create:安装一个lxc容器;
  2. lxc-create -n NAME -t TEMPLATE_NAME
  3. lxc-start:启动容器;
  4. lxc-start -n NAME -d
  5. lxc-info:查看容器相关的信息;
  6. lxc-info -n NAME
  7. lxc-console:附加至指定容器的控制台;
  8. lxc-console -n NAME -t NUMBER
  9. lxc-stop:停止容器;
  10. lxc-destory:删除处于停机状态的容器;
  11. lxc-snapshot:创建和恢复快照

2、实现容器的理论基础

2.1 NameSpace

Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的(在Linux2.6内核以前是全局的),而是属于特定的Namespace。每个Namespace里面的资源对其他Namespace都是透明的。namespace是container中使用到的重要技术之一,是对系统资源的操作上的隔离。
image.png

1)Mount namespace

是对挂载的文件系统布局进行隔离。图中显示在Namespace1中的进程看到的文件系统的挂载方式是一致的,但是在Mount Namespace2中看到的是一另一种情况
image.png

2)UTS

UTS namespace(UNIX Time Sharing)用来隔离系统的hostname以及domain name。

3)IPC

处于同一namespace下的进程才可以进行进程间通信。
image.png

4)NET

NET NAMESPACE实现网络协议栈上的隔离,在自己的namespace中对网络的设置只能在本namespace中生效。
image.png

5)PID

在不同的NameSpace中有自己的PID列表。
image.png

6)USER

User namespace中使用到了map转换,由于container并不是真正的虚拟化,所以在Guest-OS中创建的root用户会被映射到Host-OS中的普通用户中去。
image.png

2.2 CGROUP

CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。

1)主要功能

(1)限制资源使用,比如内存使用上限以及文件系统的缓存限制。
(2)优先级控制,CPU利用和磁盘IO吞吐。

2)CGROUP使用范例

安装:

[root@okd-install ~]# yum install libcgroup libcgroup-tools -y

创建一个CPU set,并分配cpu 0

[root@okd-install ~]# cgcreate -g cpuset:small
[root@okd-install ~]# cgset -r cpuset.cpus=0 small
[root@okd-install ~]# cgset -r cpuset.mems=0 small

查看结果

[root@okd-install ~]# ls /sys/fs/cgroup/cpuset
cgroup.clone_children  cpuset.cpus            cpuset.mem_hardwall             cpuset.memory_spread_page  cpuset.sched_relax_domain_level  tasks
cgroup.procs           cpuset.effective_cpus  cpuset.memory_migrate           cpuset.memory_spread_slab  notify_on_release
cgroup.sane_behavior   cpuset.effective_mems  cpuset.memory_pressure          cpuset.mems                release_agent
cpuset.cpu_exclusive   cpuset.mem_exclusive   cpuset.memory_pressure_enabled  cpuset.sched_load_balance  small

创建测试脚本

[root@okd-install ~]# vim test.sh
[root@okd-install ~]# chmod +x test.sh
[root@okd-install ~]# cat test.sh
#/bin/bash
x=0
while [ True ];do
    x=$x+1
done;

限制程序运行在固定cpu核数上

[root@okd-install ~]# cgexec -g cpuset:small ./test.sh &

查看效果:
image.png

3、Docker介绍

注意:Docker是容器,但是容器不是docker。目前K8S支持的容器运行时有如下三种:

注意:计划中,K8S 1.23版本之后将不再支持Docker。因为现有的实际环境中大多使用docker,所以本文只是以docker为例介绍容器的基本概念及常规操作。

3.1 Docker 安装

参考 “环境部署” 相关内容
在老版本中称为docker或docker-engine,如果有安装老版本,需要先卸载,参考:

yum remove docker docker-client docker-client-latest docker-common \
                  docker-latest docker-latest-logrotate \
                  docker-logrotate docker-engine

3.2 Docker 架构

image.png
Docker使用客户端-服务器架构。 Docker客户端与Docker守护程序进行对话,该守护程序完成了构建,运行和分发Docker容器的繁重工作。 Docker客户端和守护程序可以在同一系统上运行,或者您可以将Docker客户端连接到远程Docker守护程序。 Docker客户端和守护程序在UNIX Sockets或网络接口上使用REST API进行通信。 另一个Docker客户端是Docker Compose,它使您可以处理由一组容器组成的应用程序。

3.3 守护进程

Docker守护程序(dockerd)侦听Docker API请求并管理Docker对象,例如图像,容器,网络和卷。 守护程序还可以与其他守护程序通信以管理Docker服务。

[root@master ~]# systemctl status docker.service
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-02-07 15:15:47 +08; 26min ago
     Docs: https://docs.docker.com
 Main PID: 8465 (dockerd)
    Tasks: 13
   Memory: 43.9M
   CGroup: /system.slice/docker.service
           └─8465 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
......

3.4 Docker客户端

Docker客户端(docker)是许多Docker用户与Docker交互的主要方式。 当您使用诸如docker run之类的命令时,客户端会将这些命令发送至dockerd,然后执行它们。 docker命令使用Docker API。 Docker客户端可以与多个守护程序通信。

3.5 Docker Registry

Docker Registry存储Docker映像。 Docker Hub是任何人都可以使用的公共仓库,并且Docker配置为默认在Docker Hub上查找映像。 您甚至可以运行自己的私人仓库。

使用docker pull或docker run命令时,所需的映像将从配置的仓库中提取。 使用docker push命令时,会将映像推送到配置的仓库。

3.6 Docker 配置

Docker配置文件位于/etc/docker/daemon.json,可按需要进行修改。如:
镜像加速器配置:
参考:https://help.aliyun.com/document_detail/60750.html

"registry-mirrors": ["http://hub-mirror.c.163.com"],

指定http镜像仓库:

"insecure-registries" : ["myregistrydomain.com:5000"],

Debug模式:

"debug": true,

Storage Driver:

"storage-driver": "overlay2"

3.7 Docker对象

使用Docker时,您正在创建和使用映像、容器、网络、卷、插件和其他对象。

3.7.1 Image

映像是一个只读模板,其中包含创建Docker容器的说明。 通常,一个映像基于另一个映像,并进行一些其他自定义。 例如,您可以构建基于ubuntu映像的映像,但安装Apache Web服务器和您的应用程序,以及运行应用程序所需的配置详细信息。

您可以创建自己的映像,也可以仅使用其他人创建并在仓库中发布的映像。 要构建自己的映像,您可以使用简单的语法创建一个Dockerfile,以定义创建映像并运行它所需的步骤。 Dockerfile中的每条指令都会在映像中创建一个层。 当您更改Dockerfile并重建映像时,仅重建那些已更改的层。 与其他虚拟化技术相比,这是使映像如此轻巧,小型和快速的部分原因。

1)Dockerfile创建镜像

准备镜像

[root@master ~]# docker load -i /resources/images/nginx-latest.tar

[root@master ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              bc9a0695f571        11 months ago       133MB

准备文件
参考Nginx官方文档:https://hub.docker.com/_/nginx

[root@master ~]# cat index.html
AAAAAAAAAAAAAAAAAAAAAAAAAa
hello
[root@master ~]# cat Dockerfile
FROM nginx:latest
COPY index.html /usr/share/nginx/html/index.html

生成镜像

[root@master ~]# docker build -t myweb:v1 .
Sending build context to Docker daemon  3.374MB
Step 1/2 : FROM nginx:latest
 ---> bc9a0695f571
Step 2/2 : COPY index.html /usr/share/nginx/html/index.html
 ---> 6a33508c58cd
Successfully built 6a33508c58cd
Successfully tagged myweb:v1

[root@master ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myweb               v1                  6a33508c58cd        7 seconds ago       133MB
nginx               latest              bc9a0695f571        11 months ago       133MB

测试

docker run --name myweb -d myweb:v1

2)从运行容器创建镜像

运行容器

[root@master ~]# docker run --name new_web -d nginx:latest

修改容器

[root@master ~]# docker exec -it new_web bash
root@13f74b646dca:/# cd /usr/share/nginx/html/
root@13f74b646dca:/usr/share/nginx/html# ls
50x.html  index.html
root@13f74b646dca:/usr/share/nginx/html# echo "My Web2" >index.html
root@13f74b646dca:/usr/share/nginx/html# exit
exit

生成新镜像

[root@master ~]# docker commit new_web myweb:v2
sha256:d5073bbca1ce27cebb3b0fafcd2daebc967c53b5088ffd2db165edc54c238940
[root@master ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myweb               v2                  d5073bbca1ce        6 seconds ago       133MB
myweb               v1                  6a33508c58cd        24 minutes ago      133MB
nginx               latest              bc9a0695f571        11 months ago       133MB

验证:

[root@master ~]# docker run --name myweb2 -d myweb:v2
126fe69530a847df589dda8725fe83eb4d6725cd568a34a61655acd2bd6399a0
[root@master ~]# docker inspect myweb2 | grep 172
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.2",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
[root@master ~]# curl 172.17.0.2
My Web2

3.7.2 Containers

容器是映像的可运行实例。 您可以使用Docker API或CLI创建,启动,停止,移动或删除容器。 您可以将容器连接到一个或多个网络,将存储连接到它,甚至根据其当前状态创建一个新映像。

默认情况下,容器与其他容器及其主机之间的隔离度相对较高。 您可以控制容器的网络,存储或其他底层子系统与其他容器或主机之间的隔离程度。

容器由其映像以及在创建或启动时为其提供的任何配置选项定义。 删除容器后,未存储在永久性存储中的状态更改将消失。

范例:

docker run -i -t centos /bin/bash

运行此命令时,会发生以下情况(假设您使用的是默认仓库配置):

  1. 如果本地没有centos映像,则Docker会将其从配置的仓库中下载,就像您已手动运行docker pull centos一样。
  2. Docker创建一个新容器,就像您已经手动运行docker container create命令一样。
  3. Docker将一个读写文件系统分配给容器,作为其最后一层。 这允许运行中的容器在其本地文件系统中创建或修改文件和目录。
  4. Docker创建了一个网络接口,将容器连接到默认网络,因为您未指定任何网络选项。 这包括为容器分配IP地址。 默认情况下,容器可以使用主机的网络连接连接到外部网络。
  5. Docker启动容器并执行/bin/bash。 由于容器是交互式运行的,并且已附加到您的终端(由于-i和-t标志),因此您可以在输出记录到终端时使用键盘提供输入。
  6. 当您键入exit终止/bin/bash命令时,该容器停止但未被删除。 您可以重新启动或删除它

3.7.3 Persistent Storage

默认情况下,在容器内创建的所有文件都存储在可写容器层上。 这意味着:

  • 当该容器不再存在时,数据将不会持久保存,并且如果另一个进程需要该数据,则可能很难从容器中取出数据。
  • 容器的可写层与运行容器的主机紧密耦合。 您无法轻松地将数据移动到其他地方。
  • 写入容器的可写层需要存储驱动程序来管理文件系统。 存储驱动程序使用Linux内核提供联合文件系统。 与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能。

Docker为容器提供了两个选项来将文件存储在主机中,以便即使容器停止后文件也可以持久存储:卷和绑定挂载。
image.png

1)Volume

卷存储在由Docker管理的主机文件系统的一部分中(在Linux上为 /var/lib/docker/volumes/)。 非Docker进程不应修改文件系统的这一部分。 卷是在Docker中持久保存数据的最佳方法。

卷由Docker创建和管理。 您可以使用docker volume create命令显式创建卷,或者Docker可以在容器或服务创建期间创建卷。

创建卷时,它存储在Docker主机上的目录中。 将卷装入容器时,此目录就是装入容器的目录。 这类似于绑定挂载的工作方式,除了卷由Docker管理并且与主机的核心功能隔离。

一个卷可以同时挂载到多个容器中。 当没有正在运行的容器使用卷时,该卷仍可用于Docker,并且不会自动删除。 您可以使用docker volume prune删除未使用的卷。

使用范例:
1)创建卷

[root@master ~]# docker volume create my-vol
my-vol

[root@master ~]# docker volume ls
DRIVER              VOLUME NAME
local               my-vol

[root@master ~]# docker volume inspect my-vol
[
    {
        "CreatedAt": "2021-02-07T16:29:40+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

通常可以使用—mount或者-v来指定volume,—mount可以显式指定更多的选项,如需指定driver相关选择,则必须使用—mount。
2)使用—mount方式挂载卷:

[root@master ~]# docker run -d --name devtest --mount source=my-vol,target=/app nginx:latest
[root@master ~]# docker inspect devtest
        "Mounts": [
            {
                "Type": "volume",
                "Name": "my-vol",
                "Source": "/var/lib/docker/volumes/my-vol/_data",
                "Destination": "/app",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

3)使用 -v 方式挂载卷:

docker run -d --name devtest2  -v my-vol:/app  nginx:latest

2)Bind Mounts

绑定挂载可以存储在主机系统上的任何位置。 它们甚至可能是重要的系统文件或目录。 Docker主机或Docker容器上的非Docker进程可以随时对其进行修改。

从Docker早期开始就可以使用。与卷相比,绑定挂载的功能有限。使用绑定挂载时,主机上的文件或目录被挂载到容器中。该文件或目录由其在主机上的完整路径引用。该文件或目录不需要在Docker主机上已经存在。如果它还不存在,则按需创建。绑定挂载性能非常好,但是它们依赖于主机的文件系统具有特定的目录结构。如果您正在开发新的Docker应用程序,请考虑使用命名卷。

使用范例:
可以使用—mount或-v的选择进行挂载,区别是:
If you use -v or --volume to bind-mount a file or directory that does not yet exist on the Docker host, -v creates the endpoint for you. It is always created as a directory.
If you use --mount to bind-mount a file or directory that does not yet exist on the Docker host, Docker does not automatically create it for you, but generates an error.

1) —mount

docker run -d \
  -it \
  --name devtest3 \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  nginx:latest

2) -v

docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest

删除容器:

[root@master ~]# docker stop devtest
devtest
[root@master ~]# docker stop devtest2
devtest2
[root@master ~]#
[root@master ~]#
[root@master ~]# docker rm devtest2 devtest

3.7.4 Network

Docker默认支持如下几种网络类型:

  • bridge
  • host
  • overlay
  • macvlan
  • none
    [root@master ~]# docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    02333af7824a        bridge              bridge              local
    def3c66f6c3a        host                host                local
    d37f4184f4d1        none                null                local
    
    本文以Bridge为例,简单介绍。
    image.png
    默认情况下,docker使用bridge模式:
    [root@master ~]# docker run -d --name devtest centos sleep 9999
    358e4098d2fbe469c5ac3258e86c0b72b753eb11afdb00db00abefc666ed0b8f
    [root@master ~]# docker run -d --name devtest2 centos sleep 9999
    8a5c5c34a516d87fb451db1a83670f3acab00d1d710ae81d89e5ef4c7eab5b24
    [root@master ~]#
    [root@master ~]#
    [root@master ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    8a5c5c34a516        centos              "sleep 9999"        5 seconds ago       Up 4 seconds                            devtest2
    358e4098d2fb        centos              "sleep 9999"        12 seconds ago      Up 11 seconds                           devtest
    
    系统中存在两个虚拟网卡对: ``` [root@master ~]# ip a | grep veth 15: veth266143e@if14: mtu 1500 qdisc noqueue master docker0 state UP group default 17: vethc56537f@if16: mtu 1500 qdisc noqueue master docker0 state UP group default
两个网卡对连接到Bridge docker0:

[root@master ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.024231cfcd17 no veth266143e vethc56537f

分别查看两个容器IP:

[root@master ~]# docker inspect devtest2 | grep “IPAddress” “SecondaryIPAddresses”: null, “IPAddress”: “172.17.0.3”, “IPAddress”: “172.17.0.3”, [root@master ~]# [root@master ~]# docker inspect devtest | grep “IPAddress” “SecondaryIPAddresses”: null, “IPAddress”: “172.17.0.2”, “IPAddress”: “172.17.0.2”,

测试容器间网络

[root@master ~]# docker exec devtest ping -c 2 172.17.0.3 PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data. 64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.199 ms 64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.065 ms


容器网络端口映射<br />使用 -p 选项进行端口映射

docker run -d -p 80:80 nginx:latest

后台启动并运名为nginx的容器,然后将容器的80端口映射到物理机的80端口



<a name="5SSx6"></a>
## 3.8 docker 其他操作
docker pull/push<br />docker images<br />docker start/stop/restart<br />docker run<br />docker rm<br />docker exec<br />docker ps<br />docker inspect<br />docker logs<br />docker commit<br />docker cp<br />docker rmi<br />docker tag

<a name="cWgXL"></a>
# 4、完整实验
使用wordpress、mariadb镜像部署wordpress应用<br />Mariadb配置参考文档: [https://hub.docker.com/_/mariadb](https://hub.docker.com/_/mariadb)

1. 安装数据库

docker run -d —name mydb —env MYSQL_ROOT_PASSWORD=123456 —env MYSQL_DATABASE=wordpress —env MYSQL_USER=wordpress -e MYSQL_PASSWORD=123456 -p 3306:3306 mariadb


2. 安装wordpress

docker run -d —name wordpress -p 9999:80 wordpress ```

  1. 配置wordpress

浏览器访问 http://DOCKER_HOST_IP:9999 进行配置,dbserver 可指定容器IP或docker主机IP。

5、课后补充

Docker Host管理:https://www.portainer.io/
Docker Compose: https://docs.docker.com/compose/