Docker


操作系统层面的虚拟化技术

Docker核心概念

(1)镜像(Image)

Docker镜像是一个特殊的文件系统,提供容器运行时所需的程序、库、资源、配置等文件,另外还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。

镜像是一个静态的概念,不包含任何动态数据,其内容在构建之后也不会被改变。

(2)容器(Container)

镜像与容器的关系,就是面向对象编程中类与对象的关系,我们定好每一个类,然后使用类创建对象,对应到Docker的使用上,则是构建好每一个镜像,然后使用镜像创建我们需要的容器。

(3)仓库(Repository)

仓库是一个集中存储和分发镜像的服务。Docker Registry包含很多个仓库,每个仓库对应多个标签,不同标签对应一个软件的不同版本。仓库分为公开仓库(Public)和私有仓库(Private)两种形式。

最大的公开仓库是Docker Hub,是Docker提供用于存储和分布镜像的官方Docker Registry,也是默认的Registry。

Docker Hub有很多官方或其他开发提供的高质量镜像供我们使用,如果要将我们自己构建的镜像上传到Docker Hub上,我们需要在Docker Hub上注册一个账号,然后把自己在本地构建的镜像发送到Docker Hub的仓库中。

Docker基本命令

centos安装(安装前先卸载旧版本)
# 修改yum源
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# yum list docker-ce --showduplicates    # 查看docker安装列表
yum install docker-ce -y                            # 直接安装最新版本的docker-ce
service docker start                                # 启动docker
systemctl enable docker                         # 开机启动
docker verison                                             # 查看版本信息
# docker run -ti ubuntu                                # 运行镜像
ubuntu安装(安装前先卸载旧版本)

apt install apt-transport-https ca-certificates curl software-properties-common Next, install a few prerequisite packages which let apt use packages over HTTPS

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg Then add the GPG key for the official Docker repository to your system

echo "deb [arch=$(dpkg --print-architecture) 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 Add the Docker repository to APT sources

apt update Update your existing list of packages again for the addition to be recognized

apt-cache policy docker-ce Make sure you are about to install from the Docker repo instead of the default Ubuntu repo

apt install docker-ce -y install Docker

systemctl status docker Docker should now be installed, the daemon started, and the process enabled to start on boot. Check that it’s running

img

容器命令
docker create --name=nginx1 nginx              # 创建镜像 --name : 指定名称
docker run -di -v /var/docker/nginx/nginx.conf:/etc/nginx/ngix.conf -p 80:80 ai_platfrom
# -p port -v volume

docker start id|name                                         # 启动镜像
docker restart id|name                                     # 重启镜像
docker stop id|name                                          # 关闭镜像
docker exec -it <container_id> bash      # 进入容器中
docker stats  # 查看内存CPU占用情况
docker logs <container_ame>                             # 查看日志 -f :实时日记 -t:时间  -tail:输出行数
docker top <container_name>                          # 查看容器内进程
docker cp <container_name>:/path                 # /path容器文件拷贝到宿主机,反之亦然
docker diff <container_name>                         # 容器运行后文件发生的变化
docker commit <container_name>  <image_name> # 提交容器为镜像
docker rm $(docker ps -a -q)                           # 批量删除
docker export -o file_name.tar <con_id>  # 导出容器到文件 

img

修改容器启动脚本

修改容器镜像的启动命令

​ 1、Dockerfile的方式修改命令

​ 2、直接通过命令改掉

1、Dockerfile的方式修改命令

首先先获取到你要修改的镜像的名称,可以执行docker images 进行查看,然后建一个Dockerfile,文件内容如下

​ FROM image:demo #要改动命令的镜像

​ WORKDIR /root/ #执行命令的工作目录路径

​ CMD [“python”,”main.py”] # 要更改的命令

然后执行docker命令构建你的镜像

# -f Dockerfile是文件路径

# -t 是之你构建之后的镜像名称

# 最后有个.不能省略

docker build -f ./Dockerfile -t image_1:demo .

执行之后生成的镜像的启动命令会被改掉

2、直接通过命令改掉

docker commit –change=”WORKDIR /root” -c ‘CMD [“python”,”main.py”]’ container_name image_1:demo

–change 可以写入dockerfile的语法语句

-c 可以写入启动命令

最后接上修改之后的镜像名称

镜像命令
docker images                                                 # 查看所有镜像
# tag:用于区分同一仓库中的不同镜像,默认为latest
# image id :镜像唯一标识
# size : 镜像大小
# repository:仓库名称,仓库用来存放同一类镜像,没有默认为 <none>
docker run -ti <images_name>                         # 进入docker images
docker import 文件名.tar image_name:image_tag # 文件导入为镜像
docker images -a                                          # 查看所有镜像,包括中间层镜像
docker images -aq                                          # 查看所有镜像,包括中间层镜像ID
docker images imageName                                # 查看具体镜像
docker rmi imageid或者imageName             # 删除指定的镜像
docker rmi imageidA imageidB imageidC # 删除指定多个镜像
docker rmi -f imageid or imageName        # 强制删除指定的镜像
docker rmi -f $(docker images -aq)      # 删除全部的镜像
docker image inspect imageName              # 查看具体镜像详情
docker history imageName                           # 查看镜像的创建历史
docker build . -t <custom_name>                # 使用Dockerfile打包镜像 和下面的功能一直,记得 .
docker build -f dockerfilepath -t imageName:[tag] . # 构建镜像,文件在当前目录下且文件名是Dockerfile可以不写-f指定,最后的 . 代表本次执行的上下文路径是当前路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。上下文路径下不要放无用的文件,因为会一起打包发送给 docker 引擎,如果文件过多会造成过程缓慢。
docker save imageName[:tag] -o file        # 导出镜像到文件
docker load -i filePath                                # 导入镜像
docker login [repo_host:port]                     # 登录仓库,dockerhub可以不写[repo_host:port] 
docker logout [repo_host:port]                  # 登出仓库,dockerhub可以不写[repo_host:port] 
docker search imageName                                # 搜索镜像
docker tag sourceImage[:tag] targetImage[:tag] # 给镜像打标签
docker pull imageName[:tag]                      # 载镜像,不加tag就是latest
docker push imageName[:tag]                      # 推送镜像到仓库

img

Docker数据卷/挂载

我的理解:

docker 每次重启会按照指定的镜像重新创建一个容器,也就是同一个镜像可以重新创建多个容器,并同时存在的原因

所以,如果创建了新的容器,那原来的修改就没有了,所以有了挂载操作,挂载的底层原理是使用了linux的文件挂载,将数据卷挂载到容器的虚拟文件系统,在容器中的修改,实际是对宿主机的数据卷的修改,所以不会再重启后丢失修改

重启(restart)容器不会影响docker run 时的配置信息,比如端口代理,挂载配置,## 有待验证

1、数据挂载简介

在Docker中,容器的数据读写默认发生在容器的存储层,当容器被删除时,容器中的数据将会丢失。如果想实现数据的持久化,就需要将容器和宿主机建立联系(将数据从宿主机挂载到容器中),通俗的说,数据卷就是在容器和宿主机之间实现数据共享。

数据卷是宿主机(linux主机)中的一个目录或文件,当容器目录和数据卷目录绑定后,对方的修改会立即同步。可以不需要进入容器内部,就可以查看所需要的容器中的数据。

一个数据卷可以被多个容器同时挂载,一个容器也可以被挂载多个数据卷。

docker inspect -f "{{.Mounts}}" <containerName/id> 查看容器的挂载信息

注意点:

  • 不支持直接挂载文件,只能挂载文件夹
  • 想要挂载文件,必须宿主机也要有对应的同名文件

2、三种数据挂载方式

volume:挂载宿主机文件系统的固定位置(/var/lib/docker/volumes/卷名/_data)。

bind mounts:挂载宿主机系统的任意位置。

tmpfs mounts:挂载存储在宿主机系统的内存中,不会写入宿主机的文件系统。容器关闭重启数据丢失。

img

3、三种挂载方式适用场景

(1)volume(固定目录数据卷挂载)容器之间共享数据

(2)bind mounts(自定义目录挂载)主机与容器共享数据

(3)tmpfs mounts(内存挂载)既不想将数据存于主机,又不想存于容器中时(这可以是出于安全的考虑,或当应用需要写大量非持久性的状态数据时为了保护容器的性能)。

#volume 管理

docker volume ls #列出所有卷

docker volume create 卷名 #创建卷

docker volume inspect 卷名 #查看卷详细信息

docker volume rm 卷名1 卷名2 #删除卷

docker volume prune #删除未被使用的卷,容器停止的占用的卷也不会删除

#volume mounts(固定目录数据卷挂载)

docker run --mount [type=volume,]source=卷名,target=容器文件夹 镜像名

#bind mounts(自定义目录挂载)

docker run --mount type=bind,source=宿主机文件夹,target=容器内文件夹 镜像名

#tmpfs mounts(内存挂载)

docker run --mount type=tmpfs,target=容器内文件夹 镜像名

Docker网络

支持的三种模式

宿主机中的 docker0 的虚拟网卡:该网卡是一个用于桥接宿主机与容器间网络通信的虚拟网卡。当我们启动容器但没有指定网络配置时,都会使用该网卡作为网桥。

  1. 桥接模式(默认)

对于每个容器,会创建一对虚拟网卡用作映射。一个在容器中:eth0,一个在宿主机中:vethX,映射关系维护在网桥中。其他主机访问容器时,必须经过宿主机,并在网桥 docker0 中中找到 vethX 继而访问容器。这也是默认的网络模式。

img
\2. 主机模式

容器共享主机的网络空间,即容器与主机在同一网段。缺点很明显,不安全。外部可以直接访问到容器,而没有一个挡板做拦截转发。
img
\3. None 模式

每个容器中只有本地 lo 网卡,即无法与其他容器或者主机通信。最安全,除非侵入了宿主机,否则无法访问到容器。

img

我们可以使用命令:docker network ls 查看 docker 默认支持的以上三种网络模式

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
7b61cc22b3cf   bridge    bridge    local
90abe9832be3   host      host      local
69357e9d4394   none      null      local

自定义网络(推荐)

根据需求自定义配置容器网络。将会在宿主机中创建一个虚拟网卡(类似 docker0),用来关联未来在该网络环境下的容器的 vethX 虚拟网卡。可以但不限于配置:子网网段、网关、子网掩码、网络模式(桥接 or else)。
network 命令

$ docker network --help

Usage:  docker network COMMAND

Manage networks

Commands:
connect     Connect a container to a network
create      Create a network
disconnect  Disconnect a container from a network
inspect     Display detailed information on one or more networks
ls          List networks
prune       Remove all unused networks
rm          Remove one or more networks

创建

$ docker network create --help

Usage:  docker network create [OPTIONS] NETWORK

Create a network

Options:
--attachable           Enable manual container attachment
--aux-address map      Auxiliary IPv4 or IPv6 addresses used by
Network driver (default map[])
--config-from string   The network from which to copy the configuration
--config-only          Create a configuration only network
-d, --driver string        Driver to manage the Network (default "bridge")
--gateway strings      IPv4 or IPv6 Gateway for the master subnet
--ingress              Create swarm routing-mesh network
--internal             Restrict external access to the network
--ip-range strings     Allocate container ip from a sub-range
--ipam-driver string   IP Address Management Driver (default "default")
--ipam-opt map         Set IPAM driver specific options (default map[])
--ipv6                 Enable IPv6 networking
--label list           Set metadata on a network
-o, --opt map              Set driver specific options (default map[])
--scope string         Control the network's scope
--subnet strings       Subnet in CIDR format that represents a
network segment

例子:

docker network create –driver bridge –subnet 192.168.2.0/24 –gateway 192.168.2.1 my_net

使用桥接模式
子网网段为 192.168.2.0 - 192.168.2.255
子网掩码为 255.255.255.0 (/24)
网关为 192.168.2.1
名称为 my_net

检查一下宿主机中的网络设备:

$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever

...

3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:00:6b:b7:ab brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:ff:fe6b:b7ab/64 scope link
valid_lft forever preferred_lft forever

# Here!
115: br-48a9b08a5c44: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:24:0c:55:a4 brd ff:ff:ff:ff:ff:ff
inet 192.168.2.1/24 brd 192.168.2.255 scope global br-48a9b08a5c44
valid_lft forever preferred_lft forever

观察一下虚拟网卡(网桥) br-48a9b08a5c44 中的 ip 192.168.2.1/24,正是网关地址。
检察

$ docker network inspect my_net
[
  {
    "Name": "my_net",
    "Id": "59f36f39b05ae61586aa9c1480edc6e57beab18fb8f8bc68301aef434b496d6b",
    "Created": "2023-06-20T02:11:19.065380096Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
      "Driver": "default",
      "Options": {},
      "Config": [
        {
          "Subnet": "192.168.2.0/24",
          "Gateway": "192.168.2.1"
        }
      ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
      "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {},
    "Options": {},
    "Labels": {}
  }
]

(其余的命令比如 ls、删除之类的,没有必要详细解释了。根据 –help 的提示进行操作即可)
应用

  1. 启动两个容器,并应用前面创建的自定义网络 my_net
$ docker run -it -d -q --network my_net --name os1 busybox
$ docker run -it -d -q --network my_net --name os2 busybox

此时查看一下网卡配置,可以发现多出了两个用于结合 br-48a9b08a5c44 网卡进行桥接的虚拟网卡:veth3654615@if95、veth8b144b4@if97

$ ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever

...
...
...

96: veth3654615@if95: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-76115cefdaad state UP group default
link/ether 62:69:f9:e9:94:52 brd ff:ff:ff:ff:ff:ff link-netnsid 3
inet6 fe80::6069:f9ff:fee9:9452/64 scope link
valid_lft forever preferred_lft forever
98: veth8b144b4@if97: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-76115cefdaad state UP group default
link/ether 9e:2f:13:75:8f:c2 brd ff:ff:ff:ff:ff:ff link-netnsid 4
inet6 fe80::9c2f:13ff:fe75:8fc2/64 scope link
valid_lft forever preferred_lft forever
$ docker exec -it os1 sh
$ ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:C0:A8:02:02
inet addr:192.168.2.2  Bcast:192.168.2.255  Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:13 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1102 (1.0 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
inet addr:127.0.0.1  Mask:255.0.0.0
UP LOOPBACK RUNNING  MTU:65536  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
$ ping 192.168.2.3
PING 192.168.2.3 (192.168.2.3): 56 data bytes
64 bytes from 192.168.2.3: seq=0 ttl=64 time=0.118 ms
64 bytes from 192.168.2.3: seq=1 ttl=64 time=0.070 ms
64 bytes from 192.168.2.3: seq=2 ttl=64 time=0.074 ms
$ docker exec -it os2 sh
$ ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:C0:A8:02:03
inet addr:192.168.2.3  Bcast:192.168.2.255  Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:656 (656.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
inet addr:127.0.0.1  Mask:255.0.0.0
UP LOOPBACK RUNNING  MTU:65536  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
$ ping 192.168.2.2
PING 192.168.2.2 (192.168.2.2): 56 data bytes
64 bytes from 192.168.2.2: seq=0 ttl=64 time=0.096 ms
64 bytes from 192.168.2.2: seq=1 ttl=64 time=0.086 ms
64 bytes from 192.168.2.2: seq=2 ttl=64 time=0.078 ms

容器名称访问(推荐)

除了根据容器 ip,还可以根据容器名称相互访问。这样做对比直接根据 ip 访问更加灵活,无论 ip 如何变化,只要容器名称正确就可以正确访问,推荐。

$ docker exec -it os1 ping os2
PING os2 (192.168.2.3): 56 data bytes
64 bytes from 192.168.2.3: seq=0 ttl=64 time=0.083 ms
64 bytes from 192.168.2.3: seq=1 ttl=64 time=0.074 ms
64 bytes from 192.168.2.3: seq=2 ttl=64 time=0.074 ms

$ docker exec -it os2 ping os1
PING os1 (192.168.2.2): 56 data bytes
64 bytes from 192.168.2.2: seq=0 ttl=64 time=0.089 ms
64 bytes from 192.168.2.2: seq=1 ttl=64 time=0.082 ms
64 bytes from 192.168.2.2: seq=2 ttl=64 time=0.069 ms

应用默认的桥接模式

不指定–network默认使用bridge模式,启动两个容器:

$ docker run -it -d --rm --name os1 busybox
$ docker run -it -d --rm --name os2 busybox

通过容器 ip 进行访问:

# 查看各自IP
$ docker exec -it os1 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
107: eth0@if108: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

docker exec -it os2 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
111: eth0@if112: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:06 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.6/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

# 访问
$ docker exec -it os1 ping 172.17.0.6
PING 172.17.0.6 (172.17.0.6): 56 data bytes
64 bytes from 172.17.0.6: seq=0 ttl=64 time=0.083 ms
64 bytes from 172.17.0.6: seq=1 ttl=64 time=0.084 ms

$ docker exec -it os2 ping 172.17.0.5
PING 172.17.0.5 (172.17.0.5): 56 data bytes
64 bytes from 172.17.0.5: seq=0 ttl=64 time=0.109 ms
64 bytes from 172.17.0.5: seq=1 ttl=64 time=0.073 ms

在通过容器名称访问:

$ docker exec -it os1 ping os2
ping: bad address 'os2'
$ docker exec -it os2 ping os1
ping: bad address 'os1'

因为没有指定的自定义的网络,只可以通过 ip 而不能根据容器名称进行访问。那么如何也可以通过容器名称进行访问呢:–link 参数。重启 os2:

$ docker run -it -d --rm --link os1 --name os2 busybox
$ docker exec -it os2 ping os1
PING os1 (172.17.0.5): 56 data bytes
64 bytes from 172.17.0.5: seq=0 ttl=64 time=0.137 ms
64 bytes from 172.17.0.5: seq=1 ttl=64 time=0.087 ms

可以成功访问了。那么是如何做到的根据容器名称就能正常访问的呢?查看一下容器中/etc/hosts文件就明白了:

$ docker exec -it os2 cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.5      os1 c528fc3de106 # Here!
172.17.0.6      d69d0a2dfb0b

实际上就是将ip、容器名称、容器 id做了映射。

但是通过默认的桥接模式可以发现一个问题,十分的不方便,每个容器启动时都需要指定 –link。一旦容器的数量过多时,是极其麻烦的。所以,还是推荐自定义网络模式。
不同网络间的通信

前面讨论的都是同网络间的容器通信,那么不同网络间的容器是如何通信的?比如即一个使用默认 docker0 网桥的容器与使用了自定义网络的容器如何进行通信?

下面进行测试:

创建两个在不同网络间的容器:

$ docker run -it -d --name def_net_os busybox
$ docker run -it -d --name my_net_os --network my_net busybox

查看 ip 后 ping 测试:

#查看 def_net_os 的 ip

$ docker exec def_net_os ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
134: eth0@if135: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

#在 my_net_os 容器中 ping 测试
$ docker exec my_net_os ping 172.17.0.5
(无响应)

实际结果确实为无法访问。检察一下容器的网络信息以及自定义网络信息:

#容器的网络信息

$ docker inspect def_net_os
[{
  ...
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "f4a4fac0a42297c3913ad7326d5d54163f34fe2f01c5088c4361517092fa24b3",
    ...
    "Gateway": "172.17.0.1",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "172.17.0.5",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "MacAddress": "02:42:ac:11:00:05",
    "Networks": {
      # Here!
      "bridge": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": null,
        "NetworkID": "19c85157d58fecd939cf4b34aefe8f041b8a2d2b649c3c778cdc3d699df03810",
        "EndpointID": "20b1a057de5de9d9c8a64d72ff215efcc56b50abeccbf208be1ef6473c8b1e19",
        "Gateway": "172.17.0.1",
        "IPAddress": "172.17.0.5",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:11:00:05",
        "DriverOpts": null
      }
    }
  }
}]

$ docker inspect my_net_os
[{
  ...
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "34b9a9c4a5172b438d7568be0edeeadb26a56ba0db53b0a43d97c3b5ab2708e8",
    ...
    "Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "",
    "IPPrefixLen": 0,
    "IPv6Gateway": "",
    "MacAddress": "",
    "Networks": {
      # Here!
      "my_net": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": ["8ffa841b7bb2"],
        "NetworkID": "48a9b08a5c44f34a1716885fab264ea6ccf58ceaa07adbc14f3b1ae5ac0a41d8",
        "EndpointID": "8e8b5f37df1217ad6e357a4f3f191ad68a2427357b905dc110df0762e4e885ba",
        "Gateway": "192.168.2.1",
        "IPAddress": "192.168.2.2",
        "IPPrefixLen": 24,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:c0:a8:02:02",
        "DriverOpts": null
      }
    }
  }
}]

$ docker network inspect my_net
[{
  "Name": "my_net",
  "Id": "48a9b08a5c44f34a1716885fab264ea6ccf58ceaa07adbc14f3b1ae5ac0a41d8",
  "Created": "2023-06-20T13:13:52.508531488+08:00",
  ...

  # Here!
  "Containers": {
    "8ffa841b7bb24ac29a5765701bfd7025c19fe109556672ddee4ceb2f46e6e56a": {
      "Name": "my_net_os",
      "EndpointID": "8e8b5f37df1217ad6e357a4f3f191ad68a2427357b905dc110df0762e4e885ba",
      "MacAddress": "02:42:c0:a8:02:02",
      "IPv4Address": "192.168.2.2/24",
      "IPv6Address": ""
    }
  },
  "Options": {},
  "Labels": {}
}]

可以发现两个容器分别在不同的网络中。my_net 的自定义网络信息中描述了处于当前自定义网络的所有容器实例:my_net_os。

下面进行不同网络间的链接,在my_net网络中加入def_net_os容器。

connect 命令

docker network connect  <NETWORK> <CONTAINER>
$ docker network connect my_net def_net_os
$ docker exec -it my_net_os ping def_net_os
PING def_net_os (192.168.2.3): 56 data bytes
64 bytes from 192.168.2.3: seq=0 ttl=64 time=0.109 ms
64 bytes from 192.168.2.3: seq=1 ttl=64 time=0.070 ms

这时就可以在 my_net_os 中与 def_net_os 通信了。我们在检察一下 def_net_os 容器的网络信息以及自定义网络信息:

$ docker inspect def_net_os
[{
  ...
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "f4a4fac0a42297c3913ad7326d5d54163f34fe2f01c5088c4361517092fa24b3",
    ...
    "Gateway": "172.17.0.1",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "172.17.0.5",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "MacAddress": "02:42:ac:11:00:05",
    "Networks": {
      "bridge": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": null,
        "NetworkID": "19c85157d58fecd939cf4b34aefe8f041b8a2d2b649c3c778cdc3d699df03810",
        "EndpointID": "20b1a057de5de9d9c8a64d72ff215efcc56b50abeccbf208be1ef6473c8b1e19",
        "Gateway": "172.17.0.1",
        "IPAddress": "172.17.0.5",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:11:00:05",
        "DriverOpts": null
      },
      # Here!
      "my_net": {
        "IPAMConfig": {},
        "Links": null,
        "Aliases": [
          "5d73baeef4e7"
        ],
        "NetworkID": "48a9b08a5c44f34a1716885fab264ea6ccf58ceaa07adbc14f3b1ae5ac0a41d8",
        "EndpointID": "5e92f9f7767684daa035a92a153b65a334baf7f8b561ed8acff06eca41a0765b",
        "Gateway": "192.168.2.1",
        "IPAddress": "192.168.2.3",
        "IPPrefixLen": 24,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:c0:a8:02:03",
        "DriverOpts": {}
      }
    }
  }
}]

$ docker network inspect my_net
[{
  "Name": "my_net",
  "Id": "48a9b08a5c44f34a1716885fab264ea6ccf58ceaa07adbc14f3b1ae5ac0a41d8",
  "Created": "2023-06-20T13:13:52.508531488+08:00",
  ...

  "Containers": {
    # Here!
    "5d73baeef4e74a8a5808a16e12c080f0289d6ea2cf12a00aa576c6b0afe5ec93": {
      "Name": "def_net_os",
      "EndpointID": "5e92f9f7767684daa035a92a153b65a334baf7f8b561ed8acff06eca41a0765b",
      "MacAddress": "02:42:c0:a8:02:03",
      "IPv4Address": "192.168.2.3/24",
      "IPv6Address": ""
    },
    "8ffa841b7bb24ac29a5765701bfd7025c19fe109556672ddee4ceb2f46e6e56a": {
      "Name": "my_net_os",
      "EndpointID": "8e8b5f37df1217ad6e357a4f3f191ad68a2427357b905dc110df0762e4e885ba",
      "MacAddress": "02:42:c0:a8:02:02",
      "IPv4Address": "192.168.2.2/24",
      "IPv6Address": ""
    }
  },
  "Options": {},
  "Labels": {}
}]

容器与自定义网络间都维护了双方的信息,并且在 def_net_os 容器中,也新增了一个映射 my_net 网络网桥的网卡 eth1:

$ docker exec -it def_net_os ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
134: eth0@if135: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

# Here!

138: eth1@if139: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:c0:a8:02:03 brd ff:ff:ff:ff:ff:ff
inet 192.168.2.3/24 brd 192.168.2.255 scope global eth1
valid_lft forever preferred_lft forever

通过一个简单示意图总结描述一下不同网络间通信的方式:
img

docker layer

dockerfile中 RUN命令没执行一个就会生成一个layer ,如果修改一个dockerfile的最后几条RUN命令,重新打包,前面几层的docker layer会直接读取缓存中的,修改部分的将重新执行

使用docker builder prune可以清空构件中产生的所有临时文件和镜像,包括未使用的docker layer

如果使用这个命令就不能读取缓存快捷构建

Docker ENTRYPOINT

在 Dockerfile 中,ENTRYPOINT 是作为容器运行的命令存在的,但是他是非必须的,原因是除了可以在 Dockerfile 中填写 ENTRYPOINT,你还可以写 CMD,甚至同时使用两者

大部分的 Docker 镜像都定义了 ENTRYPOINT,即使不写,你依然可以从你的 base 镜像中获取到 ENTRYPOINT,前提当然是 base 镜像有定义

就算你的 Dockerfile 定义了 ENTRYPOINT,你还可以通过命令行的模式,在启动容器的时候提供 –entrypoint 选项来覆盖。下面的例子的含义是,通过 –entrypoint 来覆盖 Dockerfile 里定义的 ENTRYPOINT,然后给 CMD 命令传入 -l /tmp 的命令

$ docker run --entrypoint=/bin/ls ubuntu -l /tmp

在dockerfile中定义的

FROM ubuntu:14.04
ADD clean_log /usr/bin/clean_log
RUN chmod +x /usr/bin/clean_log   
ENTRYPOINT ["/usr/bin/clean_log"]   #将此镜像的入口点定义为clean_log脚本
CMD ["7"]    #设置ENTRYPOINT命令的默认参数是7天

ENTRYPOINT和CMD的最佳实践——总是使用数组形式的写法:如果你经常在Docker Hub上查看别人的Dockerfile,会发现数组模式(例如 CMD [“/usr/bin/command”])会比shell模式用得更多(CMD /usr/bin/command)。这是因为shell模式会自动在你提供的命令前面添加一个/bin/bash -c命令,这可能会导致意外的结果。不过有时shell模式更加有用

ENTRYPROINT和CMD搭配可以定义容器需要运行的命令,而将命令行参数留给用户提供,则使用Dockerfile中的ENTRYPOINT命令是十分方便的,可以查看案例说明

可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参

FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参 

Docker-Compose

Compose 通过一个配置文件来管理多个Docker容器,在配置文件中,所有的容器通过services来定义,然后使用docker-compose脚本来启动,停止和重启应用,和应用中的服务以及所有依赖服务的容器

Compose和Docker兼容性

img

安装
  1. github 下载二进制文件安装sudo curl -L https://github.com/docker/compose/releases/download/v3.4.0/docker-compose-uname -s-uname -m -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose添加可执行权限

docker-compose --version 检查版本

  1. pip 安装 sudo pip install docker-compose
docker-compose 文件结构
version: '3.7'
services: 

    mysql8:
        build: # 构建
          context: ./dir 
# 包含Dockerfile文件的目录路径,或者是git仓库的URL。 当提供的值是相对路径时,它被解释为相对
# 于当前compose文件的位置。 该目录也是发送到Docker守护程序构建镜像的上下文

        dockerfile: Dockerfile-alternate  
# 备用Docker文件。Compose将使用备用文件来构建,还必须指定构建路径
  
        args:  # 添加构建镜像的参数,环境变量只能在构建过程中访问
          buildno: 1  
          password: secret
        args:
          - buildno=1  # 可以传递映射或列表
          - password=secret
# webapp服务将会通过./dir目录下的Dockerfile-alternate文件构建容器镜像

        image: mysql:8.0.31
# 指定启动容器的镜像,可以是镜像仓库/标签或者镜像id(或者id的前一部分)
# 如果镜像不存在,Compose将尝试从官方镜像仓库将其pull下来,如果你还指定了build,在这种情况下,
# 它将使用指定的build选项构建它,并使用image指定的名字和标记对其进行标记

        container_name: jxscan-mysql  # 指定一个自定义容器名称,而不是生成的默认名称
# 由于Docker容器名称必须是唯一的,因此如果指定了自定义名称,则无法将服务扩展到多个容器
        network_mode:
            - frontend
            
        # restart: always
        restart: "no"
        restart: always
        restart: on-failure
        restart: unless-stopped
# no是默认的重启策略,在任何情况下都不会重启容器。 指定为always时,容器总是重新启动
# 如果退出代码指示出现故障错误,则on-failure将重新启动容器。

        hostname: mysql8
        expose:
            - "8000"
# expose:暴露端口,但不映射到宿主机,只被连接的服务访问,仅可以指定内部端口为参数


        ports: # 短格式
            - "3306:3306"
        ports: # 长格式
            - target: 80 # 容器内的端口 
              published: 8080 # 物理主机的端口 
              protocol: tcp  # 端口协议(tcp或udp) 
              mode: host # host 和ingress 两总模式,host用于在每个节点上发布主机端口,
# ingress 用于被负载平衡的swarm模式端口

          deploy:
              resources:  # 资源限制
                limits:
                  cpus: '0.5'  # 限制只能使用0.5CPU
                  memory: 512M  # 限制内存大小不能超过
                reservations:
                  memory: 256M  # 限制内存最小使用

# ports:暴露端口信息。 
# 常用的简单格式:使用宿主:容器 (HOST:CONTAINER)格式或者仅仅指定容器的端口(宿主将会随机选择
# 端口)都可以。

# 注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得
# 结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。

        volumes:
            - /etc/localtime:/etc/localtime:ro
            - /opt/jxscan/docker/mysql/config/:/etc/mysql
            - /opt/jxscan/docker/mysql/data/:/var/lib/mysql
            
# 卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro),
# 挂载数据卷的默认权限是读写(rw),可以通过ro指定为只读.你可以在主机上挂载相对路径,该路径将相
# 对于当前正在使用的Compose配置文件的目录进行扩展。 相对路径应始终以 . 或者 .. 开始。

            
            - /var/lib/mysql  # 只需指定一个路径,让引擎创建一个卷
           
            - /opt/data:/var/lib/mysql  # 指定绝对路径映射
           
            - ./cache:/tmp/cache  # 相对于当前compose文件的相对路径
           
            - ~/configs:/etc/configs/:ro  # 用户家目录相对路径
           
            - datavolume:/var/lib/mysql  # 命名卷
       
        environment:
            MYSQL_ROOT_PASSWORD: jxscan123!
            
# 重用挂载卷情况:情况一、直接复写
services:
  web1:
    build: ./web/
    volumes:
      - ../code:/opt/web/code
  web2:
    build: ./web/
    volumes:
      - ../code:/opt/web/code
      
# 重用挂载卷情况:情况二、顶级volumes
volumes:
  data-volume:
  
services:
  web1:
    build: ./web/
    volumes:
      - data-volume:/var/lib/db
  web2:
    build: ./web/
    volumes:
      - data-volume:/var/lib/backup/data
# 注意:通过顶级volumes定义一个挂载卷,并从每个服务的卷列表中引用它, 这会替换早期版本的
# Compose文件格式中volumes_from。
      
      
    redis6:
        image: redis:6.2.1
        container_name: jxscan-redis
        hostname: redis6
        restart: always
        network_mode: "bridge"
# 在 linux 上使用 host 网络模式启动容器的话,可以在本机通过 127.0.0.1 的方式访问
        ports:
            - "6379:6379"
            
        pid: "host"
# 将PID模式设置为主机PID模式。 这就打开了容器与主机操作系统之间的共享PID地址空间
# 使用此标志启动的容器将能够访问和操作裸机的命名空间中的其他容器,反之亦然。即打开该选项的容器
# 可以相互通过进程 ID 来访问和操作

        dns: 8.8.8.8
        dns:
          - 8.8.8.8
          - 9.9.9.9
# 配置 DNS 服务器。可以是一个值,也可以是一个列表

        deploy:
              replicas: 2
              update_config:
                parallelism: 2
                delay: 10s
              restart_policy:
                condition: on-failure
        volumes:
            - /etc/localtime:/etc/localtime:ro
            - /opt/jxscan/docker/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
            - /opt/jxscan/docker/redis/logs/redis-server.log:/var/log/redis-server.log
        
        
        command: redis-server /usr/local/etc/redis/redis.conf
# 覆盖容器启动后默认执行的命令。
                command: bundle exec thin -p 3000
# 该命令也可以是一个类似于dockerfile的列表:
                command: ["bundle", "exec", "thin", "-p", "3000"]
                
                
                
# links 链接到另一个服务中的容器。 请指定服务名称和链接别名(SERVICE:ALIAS),或者仅指定服
# 务名称。
web:
  links:
   - db
   - db:database
   - redis
# 链接到docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。参数格式跟 links 类似。
  external_links:
   - redis_1
   - project_db_1:mysql
   - project_db_1:postgresql
   
networks:
  frontend:
  backend:
 
volumes:
  db-data:
  
下面是定义网络的实例:
  version: "3.3"
services:
  db:
    container_name: mysql_db
    image: mysql:latest
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_ROOT_HOST=10.5.0.1
    ports:
      - 3306:3306
    volumes:
      - db:/var/lib/mysql
    networks:
      custom_network:
        ipv4_address: 10.5.0.5

volumes:
  db:
    driver: local

networks:
  custom_network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.5.0.0/16
      
          # gateway: 10.5.0.1
网关注释掉,因为报错:
ERROR: The Compose file './docker-compose.yml' is invalid because:
networks.backend.ipam.config value Additional properties are not allowed ('gateway' was unexpected)

解决办法:
config:
        - subnet: 10.5.0.0/16
        gateway: 10.5.0.1  # 网关设置在这里
        
        

AI docker-compose
version: '3.7'
services: 

    mysql:
        image: mysql_new:latest
        container_name: mysql_nwpu
        restart: always
        hostname: mysql
        networks:
            backend:
                ipv4_address: 172.18.0.3
        ports:
            - "20001:3306"
        environment:
            MYSQL_ROOT_PASSWORD: 123456

    llm:
        image: llm_garak:2.0
        restart: always
        container_name: llm
        hostname: llm
        networks:
            backend:
                ipv4_address: 172.18.0.4
        ports:
            - "20013:20013"

    ai_web_nwpu:
        image: ai_frontend:latest
        restart: always
        container_name: ai-frontend
        hostname: web
        network_mode: "host"
        ports:
            - "80:80"
        volumes:
            - /etc/localtime:/etc/localtime:ro
            - /opt/docker/ai_frontend/nginx/nginx.conf:/etc/nginx/nginx.conf
            - /opt/docker/ai_frontend/nginx/conf.d/:/etc/nginx/conf.d/
            - /opt/docker/ai_frontend/nginx/html/:/var/www/html/

    ai_evaluate_nwpu:
        image: ai_evaluate:1.1
        restart: always
        hostname: backend
        container_name: ai_evaluate
        networks:
            backend:
                ipv4_address: 172.18.0.2
        ports: 
            - "20010:20010"
        volumes:
            - /opt/docker/ai_backend/config.py:/code/ai_evaluate/function/databases/config.py

networks:
    backend:
        driver: "bridge"
        ipam:
            driver: default
            config:
                - subnet: 172.18.0.0/16

提示:您可以对此文件使用.yml或.yaml扩展名,他们都工作

与docker运行一样,默认情况下,Dockerfile中指定的选项(例如,CMD,EXPOSE,VOLUME,ENV)都被遵守,你不需要在docker-compose.yml中再次指定它们

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:
 
environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET
# 添加环境变量。 你可以使用数组或字典两种形式。 任何布尔值; true,false,yes,no需要用引号
# 括起来,以确保它们不被YML解析器转换为True或False。 
# 只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。

注意:如果你的服务指定了build选项,那么在构建过程中通过environment定义的环境变量将不会起作用。 将使用build的args子选项来定义构建时的环境变量。

docker-conpose down # 停止运行定义的容器
docker-compose up -d # 运行定义的容器

注意:YAML布尔值(true,false,yes,no,on,off)必须用引号括起来,以便解析器将它们解释为字符串

其他问题
pip 安装 docker-compose 启动报错
root@kys:/opt/docker# docker-compose up
Traceback (most recent call last):
  File "/usr/local/bin/docker-compose", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/main.py", line 81, in main
    command_func()
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/main.py", line 200, in perform_command
    project = project_from_options('.', options)
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/command.py", line 60, in project_from_options
    return get_project(
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/command.py", line 152, in get_project
    client = get_client(
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/docker_client.py", line 41, in get_client
    client = docker_client(
  File "/usr/local/lib/python3.10/dist-packages/compose/cli/docker_client.py", line 124, in docker_client
    kwargs = kwargs_from_env(environment=environment, ssl_version=tls_version)
TypeError: kwargs_from_env() got an unexpected keyword argument 'ssl_version'

docker 命令管理示例:

img

Dockerfile

  • 不对大小写敏感

指令介绍

FROM                    # 基础镜像
MAINTAINER        # 镜像是谁写的,姓名+邮箱

ARG DEBIAN_FRONTEND=noninteractive
# 这个命令很奇怪,我安装python3-tk需要交互,必须输入数字指定地区,而却还会改变自己的时区
# 使用这个命令之后没有交互,

RUN     \            # 镜像构建时需要运行的命令
# 一个 RUN 指令执行会在当前镜像的基础上创建一个新的镜像层,接下来的指令将在这个新的镜像层上执行
# RUN 语句有两种不同的形式:shell 格式和 exec 格式
      ls -l &&\ # 多行模式
      cd /etc 

ADD                    # 添加内容,类似copy 复制.tar等归档文件会先解压再复制,支持URL作为源路径,自动下载
WORKDIR            # 镜像工作目录
VOLUME            # 挂载的目录
EXPOSE            # 暴露端口配置
CMD                    # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT    # 指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD
# 当构建一个被继承DockerFile 这个时候就会运行 ONBUILD 的指令,触发指令
COPY
# 拷贝,不支持自动解压归档文件.tar,不会复制源文件的头文件夹,而是指定文件夹的内容
ENV
# 构建的时候设置环境变量

COPY ADD 如果指定目录已经存在,都不会将文件覆盖,需要先删除

注意:多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层.

多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错。

RUN书写时的换行符是\ &&\

dockerfile 需要一个 CMD ,不然docker run 可以启动,但是docker-compose启动会直接退出, 启动后没有默认的命令运行,而是立即退出

DockerFile(node环境带有gitee拉取代码)

FROM node:20
RUN mkdir /root/.ssh/
# "gitee" is the generated rsa file
ADD gitee /root/.ssh/id_rsa
RUN chmod 700 /root/.ssh/id_rsa
RUN chown -R root:root /root/.ssh
RUN touch /root/.ssh/known_hosts
RUN ssh-keyscan gitee.com >> /root/.ssh/known_hosts
# clone the project repo url
RUN git clone 
# the repository name
WORKDIR /
RUN npm install --global spa-http-server  --registry=https://registry.npm.taobao.org
RUN npm install --global @vue/cli --registry=https://registry.npm.taobao.org
EXPOSE 8080
ENV BASE_URL http://localhost:10002
# the repository name
ENTRYPOINT cd / \
    && git pull \
    && export VUE_APP_API_BASE_URL=${BASE_URL} \
    && npm install --registry=https://registry.npm.taobao.org \
    && npm run build \
    && cd /system/dist/ \
    && http-server --push-state

这是针对上面环境的示例

FROM node:14
RUN mkdir /root/.ssh/
ADD gitee /root/.ssh/id_rsa
RUN chmod 700 /root/.ssh/id_rsa
RUN chown -R root:root /root/.ssh
RUN touch /root/.ssh/known_hosts
RUN ssh-keyscan gitee.com >> /root/.ssh/known_hosts
RUN git clone git@gitee.com:fwgood/system.git
WORKDIR /system
RUN npm install --global spa-http-server  --registry=https://registry.npm.taobao.org
RUN npm install --global @vue/cli --registry=https://registry.npm.taobao.org
EXPOSE 8080
ENV BASE_URL http://localhost:10002
ENTRYPOINT cd /system \
    && git pull \
    && export VUE_APP_API_BASE_URL=${BASE_URL} \
    && npm install --registry=https://registry.npm.taobao.org \
    && npm run build \
    && cd /system/dist/ \
    && http-server --push-state

dockerfile 使用虚拟环境流程:

在本地下载好虚拟环境中项目所需的包,dockerfile中使用虚拟环境的python解释器(绝对路径)启动项目

FROM daocloud.io/library/centos:centos7

ENV PROJECT_PATH="/opt/hy/release/sa_process" \
    LANG="en_US.UTF-8"

RUN \
  mkdir -p /opt/hy/release/sa_process &&\
  mkdir -p /var/log/hy_log


# 拷贝项目代码
COPY ./init $PROJECT_PATH/init
COPY ./models $PROJECT_PATH/models
COPY ./settings $PROJECT_PATH/settings
COPY ./tasks $PROJECT_PATH/tasks
COPY ./utils $PROJECT_PATH/utils
COPY ./main.py $PROJECT_PATH/main.py
COPY ./env.tar $PROJECT_PATH/env.tar
# 换源
COPY ./docker/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
COPY ./docker/epel.repo /etc/yum.repos.d/epel.repo
# 拷贝python3.7源文件
COPY ./docker/Python-3.7.12.tar.xz /opt/Python-3.7.12.tar.xz
# 拷贝yum缓存
COPY ./docker/yum /var/cache/yum

RUN \
  # 修改时区为中国时区
  rm -f /etc/localtime && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
  # 安装开发工具
  yum -C -y groupinstall "Development tools" &&\
  yum -C -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel &&\
  yum clean all &&\
  # 安装python3.7
  mkdir /usr/local/python3 &&\
  tar xJf /opt/Python-3.7.12.tar.xz -C /opt/  &&\
  cd /opt/Python-3.7.12 && ./configure --prefix=/usr/local/python3 && make && make install &&\
  cd /opt && rm -rf /opt/Python-3.7.12*  &&\
  ln -s /usr/local/python3/bin/python3 /usr/local/bin/python3 &&\
  ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3 &&\
  # 解压项目文件
  cd $PROJECT_PATH && tar xf env.tar && rm -f env.tar

WORKDIR $PROJECT_PATH
CMD /opt/hy/release/sa_process/env/bin/python /opt/hy/release/sa_process/main.py

重新封装mysql

FROM mysql:5.7.32  # 已有的镜像
COPY evaluate1220.sql /
RUN service mysql start &&\
    mysql -uroot -p123456 -e "CREATE DATABASE evaluate" &&\
    mysql -uroot -p123456 evaluate < /evaluate1220.sql &&\
    mysql -uroot -p123456 -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456';" &&\
    mysql -uroot -p123456 -e "FLUSH PRIVILEGES;"

Docker使用MySQL

docker pull mysql 默认安装最新版

本地创建挂载目录 -v 如果安装最新版或者MySQL8.0及以下请查看第二个报错

docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:latest

命令详解:

docker run:在docker中启动一个容器实例

-d:该容器在后台运行

-p 3306:3306:容器与主机映射端口为,主机3306,容器3306

–name mysql:容器运行后的名称

-v /mysqldata/mysql/log:/var/log/mysql:将容器/var/log/mysql目录下的数据,备份到主机的 /mysqldata/mysql/log目录下

-v /mysqldata/mysql/data:/var/lib/mysql:将容器/var/lib/mysql目录下的数据,备份到主机的 /mysqldata/mysql/data目录下

-v /mysqldata/mysql/conf:/etc/mysql:将容器/etc/mysql目录下的数据,备份到主机的 mysqldata/mysql/conf目录下

-e MYSQL_ROOT_PASSWORD=root:设置当前mysql实例的密码为root

mysql:5.7:需要运行的容器名称以及版本号

docker exec -ti mysql bash 进入容器

mysql -u root -p 输入密码登入MySQL

远程访问设置

create user 'admin'@'%' identified by '10217'; 创建账户用于远程

grant all on *.* to 'admin'@'%'; 赋予所有权限

alter user 'admin'@'%' identified with mysql_native_password by '10217'; 设置密码

flush privileges; 刷新权限

将现有的镜像重新打包成新的镜像

FROM mysql:5.7.32  // 现有的镜像名称
COPY evaluate1220.sql /
RUN service mysql start &&\
        mysql -uroot -p123456 -e "CREATE DATABASE evaluate" &&\
        mysql -uroot -p123456 evaluate < /evaluate1220.sql &&\
        mysql -uroot -p123456 -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456';" &&\
        mysql -uroot -p123456 -e "FLUSH PRIVILEGES;"
报错记录
1. 运行容器并挂载目录出错

报错代码段

[root@VM-12-12-centos ~]# docker run -p 3306:3306 --name mysql \
> -v /mydata/mysql/log:/var/log/mysql \
> -v /mydata/mysql/data:/var/lib/mysql \
> -v /mydata/mysql/conf:/etc/mysql \
> -e MYSQL_ROOT_PASSWORD=root \
> -d mysql:latest
79c8a53d443ce5de8a78a0851f00f9a46f8868e9d5c0047c9981773af127e7e8
docker: Error response from daemon: driver failed programming external 
connectivity on endpoint mysql (8699581b7317243f12940ca11315b7e0846a2afe01f9e97
16035d3db240458c6):  (iptables failed: iptables --wait -t filter -A DOCKER ! -i
docker0 -o docker0 -p tcp -d 172.17.0.2 --dport 3306 -j ACCEPT: iptables: No 
chain/target/match by that name.

按照要求执行代码

iptables --wait -t filter -A DOCKER ! -i docker0 -o docker0 -p tcp -d 172.17.0.2 --dport 3306 -j ACCEPT

但是依旧报错:

iptables: No chain/target/match by that name.

解决办法

service docker restart 重启再执行命令即可生效

2. 生成的容器无法运行起来
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:latest

以上配置不适用于mysql8.0及以上(目前了解到的)

docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:latest
3. 远程连接3306连接不上

1.是否有权限访问MySQL

2.MySQL的服务为开启或已关闭

select User,host from mysql.user 打印结果host值有%就代表有远程访问权限

3.MySQL正在不同的端口上运行,连接端口错误,检查端口 netstat -ntlp

4.Linux防火墙阻止了与MySQL的连接,检查firewall或者iptables -L 查看iptables规则

5.是否被云服务器防火墙拦截,去网站的防火墙进行开放3306端口 <-- 一般都是这个原因

Docker 使用MongoDB

docker pull mongo

docker run –name mongo -p 27017:27017 -tid mongo:latest

–auth 需要验证就加,不需要验证,就去掉。默认mongodb是不使用用户认证

# 创建mongo容器后,进入容器

docker exec -it mongodb /bin/bash

# 进入mongo shell

mongo 6.0版本之前使用

mongosh 6.0版本之后使用

注:如果出现:bash: mongo: command not found

这是因为MongoDB 6.0全面弃用了mongo而使用mongosh。

mongod –version 查看mongo版本

# 进入admin数据库

use admin

# 创建超级权限root角色的用户,或者userAdminAnyDatabase 等角色权限比较大的用户,我创建的是拥有超级权限root角色的用户root

# 用户名:root,密码:root,角色:root,数据库:admin

db.createUser({user:’root’,pwd:’10217-op’,roles:[{role:’root’,db:’admin’}]})

6.0之后创建成功返回的成功标识为:{ok:1}

# 认证登录db.auth(‘用户名’,’密码’),打印1则代表认证通过

db.auth(‘root’,’10217-op’)

创建普通用户、密码和数据库

​ 以 admin 用户身份进入mongo :

docker exec -it mongodb mongo admin

​ 创建 用户、密码和数据库:

db.createUser({ user: ‘web’, pwd: ‘web123456’, roles: [ { role: “readWrite”, db: “web” } ] });

​ 对 web用户 进行身份认证:

use web;

db.auth(“web”,”web123456”);

docker Redis

docker pull redis 下载最新的redis

docker run --restart=always --log-opt max-size=100m --log-opt max-file=2 -p 6379:6379 --name redis -v /home/redis/myredis/myredis.conf:/etc/redis/redis.conf -v /home/redis/myredis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes --requirepass 000415 配置并运行redis

  1. –restart=always 总是开机启动
  2. –log是日志方面的
  3. -p 6379:6379 将6379端口挂载出去
  4. –name 给这个容器取一个名字
  5. -v 数据卷挂载
  • /home/redis/myredis/myredis.conf:/etc/redis/redis.conf 这里是将 liunx 路径下的myredis.conf 和redis下的redis.conf 挂载在一起。
  • /home/redis/myredis/data:/data 这个同上
  1. -d redis 表示后台启动redis
  2. redis-server /etc/redis/redis.conf 以配置文件启动redis,加载容器内的conf文件,最终找到的是挂载的目录 /etc/redis/redis.conf 也就是liunx下的/home/redis/myredis/myredis.conf
  3. –appendonly yes 开启redis 持久化
  4. –requirepass 000415 设置密码 (如果你是通过docker 容器内部连接的话,就随意,可设可不设。但是如果想向外开放的话,一定要设置,我被搞过,可以看这篇文章“阿里云服务器中毒‘Kirito666’经历”)
  5. 成功界面

docker logs --since 30m <containerName> 查看近30min的日志,检查启动情况

docker inspect <containerName> 查看容器信息

docker exec -it myredis redis-cli 使用 redis-cli 输出

redis.conf
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
#bind 127.0.0.1

protected-mode no

port 6379

tcp-backlog 511

requirepass 000415

timeout 0

tcp-keepalive 300

daemonize no

supervised no

pidfile /var/run/redis_6379.pid

loglevel notice

logfile ""

databases 30

always-show-logo yes

save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes

rdbcompression yes

rdbchecksum yes

dbfilename dump.rdb

dir ./

replica-serve-stale-data yes

replica-read-only yes

repl-diskless-sync no

repl-disable-tcp-nodelay no

replica-priority 100

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

appendonly yes

appendfilename "appendonly.aof"

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

aof-load-truncated yes

aof-use-rdb-preamble yes

lua-time-limit 5000

slowlog-max-len 128

notify-keyspace-events ""

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

list-max-ziplist-size -2

list-compress-depth 0

set-max-intset-entries 512

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

hll-sparse-max-bytes 3000

stream-node-max-bytes 4096
stream-node-max-entries 100

activerehashing yes

hz 10

dynamic-hz yes

aof-rewrite-incremental-fsync yes

rdb-save-incremental-fsync yes

其他

docker build 多次提示没有空间
docker builder prune // remove all builde cache
// Options:
//   -a, --all                  Remove all unused images, not just dangling ones
//       --filter filter        Provide filter values (e.g. 'unused-for=24h')
//   -f, --force                Do not prompt for confirmation
//       --keep-storage bytes   Amount of disk space to keep for cache
dockerfile 执行RUN命令报错127

source 是 bashshell 的内置命令,可以激活 virtualenv 的虚拟环境

报错如下:

/bin/sh -c source ... 使用bin/sh 找不到source命令
RUN /bin/bash -c "source /user/ai_backend/venv/bin/activate"
容器启动失败调试

https://www.cnblogs.com/sylvia-liu/p/14933776.html

Docker命令使用报错

docker search mysql 或者 docker pull 这些命令无法使用

出现如下错误:

Error response from daemon: Get https://index.docker.io/v1/search?q=mysql&n=25: x509: certificate has expired or is not yet valid

原因分析:

这个错误的原因在于是系统的时间和docker hub时间不一致,需要做系统时间与网络时间同步。

解决方法:

sudo yum -y install ntp ntpdate 安装时间同步

sudo ntpdate cn.pool.ntp.org.同步时间

date查看本机时间

Docker删除镜像/容器
  1. 停止该镜像的所有容器

docker ps 查看运行的容器

docker stop <container_id> 停止容器(只取前三个字母即可)

  1. 删除该镜像的所有容器

docker ps -a 查看所有存在的镜像

docker rm <container_id> 移除镜像下的所有容器(只取前三个字母即可)

  1. 删除镜像(删除镜像前必须先删除容器)

docker images 查看镜像

docker rmi <image_id> 移除镜像(只取前三个字母即可)

Docker 清理
docker builder prune # 命令用于清理构建过程中产生的临时文件和缓存,包括未使用的镜像层。它可以帮助释放磁盘空间并清理不再需要的构建缓存
docker container prune # 清理停止状态的容器。运行该命令将删除所有已停止的容器,释放磁盘空间。
docker image prune # 清理无用的镜像。运行该命令将删除所有未被任何容器使用的镜像,释放磁盘空间。
docker network prune # 清理无用的网络。运行该命令将删除所有未被任何容器使用的网络,释放资源。
docker volume prune # 清理无用的卷。运行该命令将删除所有未被任何容器使用的卷,释放磁盘空间。
卸载旧版本docker

官方卸载命令

$ sudo apt-get purge docker-ce docker-ce-cli containerd.io
$ sudo rm -rf /var/lib/docker

手动清理[确保删除干净]

# 删除某软件,及其安装时自动安装的所有包
sudo apt-get autoremove docker docker-ce docker-engine docker.io containerd runc
 
# 删除docker其他没有没有卸载
dpkg -l | grep docker
dpkg -l |grep ^rc|awk{print $2}|sudo xargs dpkg -P # 删除无用的相关的配置文件
 
# 卸载没有删除的docker相关插件(结合自己电脑的实际情况)
sudo apt-get autoremove docker-ce-*
 
# 删除docker的相关配置&目录
sudo rm -rf /etc/systemd/system/docker.service.d
sudo rm -rf /var/lib/docker
 
# 确定docker卸载完毕
docker --version
Docker更换镜像源

创建或修改 /etc/docker/daemon.json 文件,修改:

{
  "registry-mirrors" : [
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://cr.console.aliyun.com",
    "https://mirror.ccs.tencentyun.com"
  ]
}

重启docker服务使配置生效:

systemctl daemon-reload
systemctl restart docker.service

查看配置是否成功:

docker info
docker调试

container 为了 进程的存在而存在,如果主进程没有container也启动不起来,所以如果要单独运行程序,需要将程序开在其他进程,主进程保持运行即可

如果在docker中有编辑环境比如ubuntu可以将主进程设为一个其他程序,或者死循环,进入容器手动开启程序进行调试

查看容器IP
docker inspect --format='{{.NetworkSettings.IPAddress}}' <容器名称或 ID>
Docker build 无法解析域名

开始进行 docker build 构建镜像时,就遇到了以下的问题:

[root@localhost dockerfile1]# docker build -t xx:xx .

...
 => ERROR [2/2] RUN yum install telnet -y     && yum clean all     && rm -rf /var/cache/yum/*
------
 > [2/2] RUN yum install telnet -y     && yum clean all     && rm -rf /var/cache/yum/*:
0.443 Loaded plugins: fastestmirror, ovl
0.561 Determining fastest mirrors
21.11 Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os&infra=container error was
21.11 14: curl#6 - "Could not resolve host: mirrorlist.centos.org; Unknown error"

解决方法:在docker的daemon.json中追加DNS服务器地址。

# 追加到 /etc/docker/daemon.json,注意JSON的格式
{"dns":["8.8.8.8","114.114.114.114"]}
# 修改完daemon.json之后
systemctl daemon-reload
systemctl docker restart
# 此时docker可以正常构建镜像了

测试不管用

docker仓库源
  • [Docker Hub](官方提供的公共镜像仓库,包含大量的官方和社区维护的镜像
  • [阿里云容器镜像服务](Docker镜像的下载
  • [DaoCloud加速器提供的Docker镜像加速服务,可以加速国内Docker镜像的下载
{"registry-mirrors":["https://almtd3fa.mirror.aliyuncs.com"]}
{"registry-mirrors":["https://registry.docker-cn.com"]}
启动服务作为容器进程

服务作为容器进程会无法执行,因为服务启动后在后台执行,可知:

容器执行的命令需要的进程是前台运行的,需要将服务放到前台运行,加上 -n参数就是前台运行

容器死循环

容器命令可以使用思璇换可以将容器保持启动状态,死循环如:tail -f /dev/null


文章作者: Nico
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Nico !
  目录