本笔记以个人思维为导向讲述docker的操作,以实验用到什么聊什么为叙述指导,不包含太多关于原理性内容,并且自身为运维,故整篇文章还是偏重于运维角度的,距通用文档水平相去尚远,更算不得一篇原理性笔记,只是操作。综上这篇笔记还是比较合适对容器已经有一定概念的运维同学来一起学习使用。
并且本笔记不包含docker compose与docker swarm的内容,推荐在掌握了docker基础后使用kubernetes,故此不再过多的涉及docker的编排方面。
本文实验环境:
基于 Centos7.9(通篇以linux为基本讲述,mac os及windows不涉及)
docker版本:docker-ce-20.10.7
每节点 2核心2G内存40G磁盘
节点概况:
IP | 主机名 | 角色 |
---|---|---|
192.168.1.101 | node01 | docker测试 |
192.168.1.102 | node02 | docker测试 |
192.168.1.110 | harbor | harbor |
修改记录:
版本 | 修改时间 | 备注 |
---|---|---|
v.00 | 2021.6 | systemctl529_Yu |
注:
一定请看好每条命令前的主机名,本笔记以主机名区分不同主机操作
容器,顾名思义,即为承载其他“物体”的器具,如生活中的锅碗瓢盆,他们都是物理世界中真实可触碰的载具,我们这里提到的容器,是软件层级的逻辑容器。
关于容器技术的发展,最早被认为是容器的技术,出现在1979年,Unix版本7在开发过程中引入Chroot Jail以及Chroot系统调用。Chroot jail被用于“Change Root”,它被认为是最早的容器化技术之一。(相信应该有不少人在单用户模式下使用过chroot /sysroot)
后来在21世纪初期出现了众多的容器相关的技术,比如Linux-Vserver,比如Cgroups LXC等等
而我们今天讨论的docker,第一个开源版本出现于2013年,但现今为止,类似docker的这种容器引擎,存在有众多的发行版本,截止目前,docker仍然是容器引擎的主流
docker公司原本叫做dotCloud,做PaaS项目,然而经营并不好,13年他们开源了docker,竟突然获得巨大成功,所以公司也就改名叫做docker Inc了,docker公司的CTO Solomon Hykes,是docker的灵魂人物,也正是他提出了现在注明的:“Build Once,Run Anywhere”,一次构建,到处运行。
探讨一个问题,我们已经有了相当成熟的Iaas虚拟化架构,为何又需要paas,我个人认为主要还是来源于效率的需求,传统虚拟化总是无法绕过“客户机操作系统”,即便是一个经过了精心裁剪的系统,其也无法摆脱一个独立的客户机系统内核及基本运行环境等的资源占用,而容器技术一大区别就是其不再需要额外的虚拟化出一套完整的系统,其内核直接共用宿主机系统,只需要封装一个基本的软件所需运行环境和项目代码即可了,这就使其足够的轻量化,减轻了一些资源的浪费,并且因为一个一个简洁的镜像和运行时,docker变得要比传统软件部署一定方面上更易于管理,再加上容器一旦配合上一些编排技术,那么其快速的扩缩容能力和通用部署能力也非传统虚拟化可比。当然了,目前来看,任何技术都并非完美的,虚拟化亦有其自身目前难以被替代的优势,比如其真正的资源隔离,对物理机的资源集群整合等。而且其实目前大部分的容器集群,真正的利用环境亦是运行在虚拟机之上的,真正具备物理机直接运行容器的企业,还是相对较大型的体量,总体较少,所以容器目前对于中小企业来看,仍然是前瞻意义大于实际意义的。
容器的优势和劣势在哪里?简单来说,容器的主要劣势还是存在于隔离性上面,优势就较多了,比如资源消耗小,启动速度快,跨平台能力强,扩展快,对微服务支持好,对自动化架构配合好等
常见的传统虚拟化:
以下是一个 2 型虚拟化的基本结构图:
传统的虚拟化通常有两种,1型虚拟化(如xen esxi)或者2型虚拟化(kvm workstation),不论是1型还是2型,都无法离开“Guest OS”,这也就会带来一定的资源额外消耗,
容器的虚拟化
容器也是一种虚拟化技术,只是它并非是“平台虚拟化”,而是“操作系统虚拟化”,也就是说,其虚拟的内容已经不再是传统虚拟化的硬件资源,而是应用运行环境
以下是容器的基本结构图:
可以看到,容器是直接运行在宿主机系统之上的,不再需要一个额外的操作系统来作为应用运行环境,故这种形式,也可以将一个容器看作是一个“进程”
既然容器直接跑在宿主机中,那么资源的隔离和限制怎么来实现,到目前,解决方案已经直接集成在了系统内核中
docker通过系统内核中的 namespace实现资源隔离,通过cGroups实现资源限制,那么一个容器需要隔离出哪些基本资源?
namespace资源隔离
资源隔离内容 | 集成于哪个内核版本 |
---|---|
Mount(文件系统,挂载点…) | 2.4.19 |
Pid (进程ID) | 2.6.24 |
IPC (信号量,消息队列,共享内存…) | 2.6.19 |
Net (网络设备,网络协议栈,端口…) | 2.6.29 |
UTS (主机名,域名…) | 2.6.19 |
User(操作进程的用户及用户组) | 3.8 |
namespace隔离的系统调用参数及基本解释
mount CLONE_NEWNS 每个容器的文件系统是独立的
pid CLONE_NEWPID 每个容器内部有独立的进程树
IPC CLONE_NEWIPC 每个容器依旧使用linux内核中进程交互的方法,实现进程间通信
Net CLONE_NEWNET 每个容器之间的网络默认隔离
UTS CLONE_NEWUTS 每个namesapce有自己独立的主机名或者域名
User CLONE_NEWUSER 每个容器都有独立的root,独立的用户
cGroups
cgroups(Control Groups) 是 linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:
资源限制:cgroups 可以对任务是要的资源总额进行限制,比如设定任务运行时使用的内存上限,一旦超出就发OOM。
优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。
资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。
任务控制:cgroups 可以对任务执行挂起、恢复等操作。
docker引擎是基于Google公司的Go语言来编写实现,Golang号称21世纪C语言,当前非常热门,目前来看,Go语言的前景也确实够好。
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
docker部署时,可以直接安装二进制软件包,如果想要源码编译,也就需要一个Go的编译环境。
关于系统环境的基本初始化就不再赘述了,仍然是关掉防火墙,Selinux这一套
另外可能唯一和之前不同的是,需要开一下路由转发
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# sysctl -p
我们安装的docker,是docker-ce(社区版),企业版是docker-ee
因为这里使用的是Centos(Rpm包管理),故需要配置的是yum源
首先配置基础源,可以是系统默认的,也可以自己手动新增国内源,然后配置docker源
# 配置清华源
[root@node01 yum.repos.d]# pwd
/etc/yum.repos.d
# 注意,国内源中缺少一些包,不要删除掉系统自带的repo文件,只新增即可
[root@node01 yum.repos.d]# vim tuna.os.repo
[tuna_centos]
name=tuna.centos.os.yum
baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/os/x86_64/
enabled=1
gpgcheck=0
# epel源
[root@node01 yum.repos.d]# vim tuna.epel.repo
[tuna.epel]
name=tuna.epel
baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/x86_64/
enabled=1
gpgcheck=0
# 配置docker源
[root@node01 yum.repos.d]# vim tuna.docker.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/gpg
或者
[root@node01 yum.repos.d]# wget https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
[root@node01 yum.repos.d]# sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
# 缓存
[root@node01 yum.repos.d]# yum clean all
[root@node01 yum.repos.d]# yum makecache
# 查看最新的docekr版本
[root@node01 yum.repos.d]# yum list docker-ce
已加载插件:fastestmirror
Loading mirror speeds from cached hostfile
可安装的软件包
docker-ce.x86_64 3:20.10.7-3.el7 docker-ce-stable
列出源中所有软件包版本可以执行
[root@node01 yum.repos.d]# yum list docker-ce --show-duplicates
也可以只更新kernel相关包
[root@node01 yum.repos.d]# yum update
[root@node01 ~]# yum -y install docker-ce
[root@node01 ~]# docker -v
Docker version 20.10.7, build f0df350
# 启动并设置开机自启
[root@node01 ~]# systemctl start docker
[root@node01 ~]# systemctl enable docker
# 配置docker
[root@node01 ~]# vim /etc/docker/daemon.json
{
"data-root": "/docker",
"bip": "172.1.1.1/16",
"storage-driver": "overlay2"
}
[root@node01 ~]# systemctl daemon-reload
daemon.json配置项简析:
首先需要一个 {},然后每行配置的间隔需要使用 , 号,最后一行可以不加 ,号
“exec-root”:“/docker” exec-root配置的是docker工作目录
“registry-mirrors”: [“https://xxxxxxxxxxxxxx.mirror.aliyuncs.com”] registry-mirrors是配置的镜像加速,如果有多个,可以[“1”,‘’2“]继续写
“insecure-registries”: [“域名/IP:port”] insecure-registries配置的是私有非ssl镜像仓库地址,如果有多个,可以[“1”,“2”]继续写
“storage-driver”: “overlay2” storage-driver存储驱动程序
\“bip”: “172.1.1.1/16” bip docker网络
“live-restore”: true live-restore传递一个SIGHUP信号给daemon进程来重载配置
docker-daemon.json各配置详解 { “api-cors-header”:"", ——————在引擎API中设置CORS标头 “authorization-plugins”:[], ——————要加载的授权插件 “bridge”:"", ————将容器附加到网桥 “cgroup-parent”:"", ——————为所有容器设置父cgroup “cluster-store”:"", ——————分布式存储后端的URL “cluster-store-opts”:{ }, ————————设置集群存储选项(默认map []) “cluster-advertise”:"", ————————要通告的地址或接口名称 “debug”: true, ————————启用调试模式,启用后,可以看到很多的启动信息。默认false “default-gateway”:"", ——————容器默认网关IPv4地址 “default-gateway-v6”:"", ——————容器默认网关IPv6地址 “default-runtime”:“runc”, ————————容器的默认OCI运行时(默认为“ runc”) “default-ulimits”:{ }, ——————容器的默认ulimit(默认[]) “dns”: [“192.168.1.1”], ——————设定容器DNS的地址,在容器的 /etc/resolv.conf文件中可查看。 “dns-opts”: [], ————————容器 /etc/resolv.conf 文件,其他设置 “dns-search”: [], ————————设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的 主机时,DNS不仅搜索host,还会搜 索host.example.com 。 注意:如果不设置, Docker 会默认用主机上的 /etc/resolv.conf 来配置容器。 “exec-opts”: [], ————————运行时执行选项 “exec-root”:"", ————————执行状态文件的根目录(默认为’/var/run/docker‘) “fixed-cidr”:"", ————————固定IP的IPv4子网 “fixed-cidr-v6”:"", ————————固定IP的IPv6子网 “data-root”:"/var/lib/docker", ————-Docker运行时使用的根路径,默认/var/lib/docker “group”: “”, ——————UNIX套接字的组(默认为“docker”) “hosts”: [], ——————设置容器hosts “icc”: false, ——————启用容器间通信(默认为true) “ip”:“0.0.0.0”, ————————绑定容器端口时的默认IP(默认0.0.0.0) “iptables”: false, ———————启用iptables规则添加(默认为true) “ipv6”: false, ——————启用IPv6网络 “ip-forward”: false, ————————默认true, 启用 net.ipv4.ip_forward ,进入容器后使用 sysctl -a | grepnet.ipv4.ip_forward 查看 “ip-masq”:false, ——————启用IP伪装(默认为true) “labels”:[“nodeName=node-121”], ————————docker主机的标签,很实用的功能,例如定义:–label nodeName=host-121 “live-restore”: true, ——————在容器仍在运行时启用docker的实时还原 “log-driver”:"", ——————容器日志的默认驱动程序(默认为“ json-file”) “log-level”:"", ——————设置日志记录级别(“调试”,“信息”,“警告”,“错误”,“致命”)(默认为“信息”) “max-concurrent-downloads”:3, ——————设置每个请求的最大并发下载量(默认为3) “max-concurrent-uploads”:5, ——————设置每次推送的最大同时上传数(默认为5) “mtu”: 0, ——————设置容器网络MTU “oom-score-adjust”:-500, ——————设置守护程序的oom_score_adj(默认值为-500) “pidfile”: “”, ——————Docker守护进程的PID文件 “raw-logs”: false, ——————全时间戳机制 “selinux-enabled”: false, ——————默认 false,启用selinux支持 “storage-driver”:"", ——————要使用的存储驱动程序 “swarm-default-advertise-addr”:"", ——————设置默认地址或群集广告地址的接口 “tls”: true, ————————默认 false, 启动TLS认证开关 “tlscacert”: “”, ——————默认 ~/.docker/ca.pem,通过CA认证过的的certificate文件路径 “tlscert”: “”, ————————默认 ~/.docker/cert.pem ,TLS的certificate文件路径 “tlskey”: “”, ————————默认~/.docker/key.pem,TLS的key文件路径 “tlsverify”: true, ————————默认false,使用TLS并做后台进程与客户端通讯的验证 “userland-proxy”:false, ——————使用userland代理进行环回流量(默认为true) “userns-remap”:"", ————————用户名称空间的用户/组设置 “bip”:“192.168.88.0/22”, ——————————指定网桥IP “registry-mirrors”: [“https://192.498.89.232:89”], ————————设置镜像加速 “insecure-registries”: [“120.123.122.123:12312”], ———————设置私有仓库地址可以设为http “storage-opts”: [ “overlay2.override_kernel_check=true”, “overlay2.size=15G” ], ————————存储驱动程序选项 “log-opts”: { “max-file”: “3”, “max-size”: “10m”, }, ————————容器默认日志驱动程序选项 “iptables”: false ————————启用iptables规则添加(默认为true) }
# 执行docker info 可以查看基本的docker信息
[root@node01 docker]# docker info
Client:
Context: default
Debug Mode: false
Plugins:
app: Docker App (Docker Inc., v0.9.1-beta3)
buildx: Build with BuildKit (Docker Inc., v0.5.1-docker)
scan: Docker Scan (Docker Inc., v0.8.0)
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 20.10.7
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
Default Runtime: runc
Init Binary: docker-init
containerd version: d71fcd7d8303cbf684402823e425e9dd2e99285d
runc version: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
init version: de40ad0
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-1160.11.1.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 1.933GiB
Name: node01
ID: WIDC:DX4D:7IW7:H5OR:EK4Y:F4YX:ABUI:IKE7:DVIN:K5W3:KE76:6YAO
Docker Root Dir: /docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
https://xxxxxx.mirror.aliyuncs.com/
Live Restore Enabled: false
如果执行 docker info时,发现了以下两条警告
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled这意味着你的防火墙没有关闭,如果不想关,就增加下配置,以要求iptables不对bridge的数据进行处理
# vim /etc/sysctl.conf # 追加 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 # sysctl -p net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1
提前做一下docker镜像加速的配置,因为后面运行docker需要先拉取docker镜像,但是docker hub是在国外的,对我们而言,很慢,所以先配置下加速
这里以阿里云镜像加速为例,首先记得注册好阿里云账号
配置下加速地址
[root@node01 ~]# vim /etc/docker/daemon.json
{
### 追加以下一行
"registry-mirrors": ["https://xxxxxxxxxxxx.mirror.aliyuncs.com"]
}
[root@node01 ~]# systemctl daemon-reload
[root@node01 ~]# systemctl restart docker
docker对镜像或者容器的操作,使用ID或者名称均可
docker镜像,就是打包好的应用以及应用环境(代码,运行时,库,环境变量,配置文件等)
# docker images 查看镜像列表
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
搜索镜像可以在命令行搜索,也可以去比如docker hub等镜像仓库搜索
[root@node01 ~]# docker search centos
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
centos The official build of CentOS. 6594 [OK]
ansible/centos7-ansible Ansible on Centos7 134 [OK]
consol/centos-xfce-vnc Centos container with "headless" VNC session… 129 [OK]
jdeathe/centos-ssh OpenSSH / Supervisor / EPEL/IUS/SCL Repos - … 118 [OK]
......
docker 官方镜像仓库:https://registry.hub.docker.com/
拉取镜像时,如果不指定版本,那么拉取的就是 latest 最新版本,指定版本 # docker pull centos:TAG
[root@node01 ~]# docker pull centos
Using default tag: latest
latest: Pulling from library/centos
7a0437f04f83: Downloading 39.37MB/75.18MB
[root@node01 ~]# docker pull centos:7.9.2009
7.9.2009: Pulling from library/centos
2d473b07cdd5: Downloading 22.57MB/76.1MB
# 查看下本地镜像
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 300e315adb2f 6 months ago 209MB
centos 7.9.2009 8652b9f0cb4c 7 months ago 204MB
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 300e315adb2f 6 months ago 209MB
centos 7.9.2009 8652b9f0cb4c 7 months ago 204MB
[root@node01 ~]# docker rmi centos:latest
Untagged: centos:latest
Untagged: centos@sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
Deleted: sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55
Deleted: sha256:2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7.9.2009 8652b9f0cb4c 7 months ago 204MB
# 导出
[root@node01 ~]# docker save centos:7.9.2009 -o /tmp/centos.7.9.2009
[root@node01 ~]# du -sh /tmp/centos.7.9.2009
202M /tmp/centos.7.9.2009
# 导入
先删除本地的centos:7.9.2009镜像
[root@node01 ~]# docker rmi centos:7.9.2009
Untagged: centos:7.9.2009
Untagged: centos@sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e
Deleted: sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf
Deleted: sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02
[root@node01 ~]# docker load < /tmp/centos.7.9.2009
174f56854903: Loading layer 211.7MB/211.7MB
Loaded image: centos:7.9.2009
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7.9.2009 8652b9f0cb4c 7 months ago 204MB
如果导入镜像后,查看时,发现镜像名为 ,这种可以使用 docker tag 来命名
# docker tag [镜像ID] 镜像名:TAG
也就是在运行的容器,运行起来的镜像;其与镜像的区别:镜像是静态的定义,容器是镜像运行时的实体
关于容器生命周期
docker run [选项 参数] 镜像 CMD
# 当本地没有你要运行的镜像时,会自动拉取
[root@node01 ~]# docker pull centos:latest
[root@node01 ~]# docker run centos
# 查看运行中的容器
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
刚运行的容器并没有存在,这是因为我们运行时没有指定CMD运行内容,有些镜像比如这种系统镜像,是需要手动指定运行内容的
# 查看所有的容器(包含未运行的)
[root@node01 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36a3d9354ba4 centos "/bin/bash" 37 seconds ago Exited (0) 36 seconds ago confident_visvesvaraya
常用选项
-d: 后台运行容器,并返回容器ID;
-i: 以交互模式运行容器,通常与 -t 同时使用;
-P: 随机端口映射,容器内部端口随机映射到主机的端口
-p: 指定端口映射,格式为:主机(宿主)端口:容器端口
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
–name=“容器名”: 为容器指定一个名称;
**-m **:设置容器使用内存最大值;
–net=“bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
–expose=[]: 开放一个端口或一组端口;
–volume , -v: 绑定一个卷
-e: 传递环境变量 环境变量key:环境变量value
# 运行bash程序
[root@node01 ~]# docker run -itd centos:latest /bin/bash
3b1157fa7e1b34b1c288de81da35cd8e310ff14ec4ea8d16d64cb4d60da96c09
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b1157fa7e1b centos:latest "/bin/bash" 2 seconds ago Up 1 second sharp_shaw
[root@node01 ~]# docker run -itd --name="centos_v1" centos:latest /bin/bash -c "while true;do echo hello docker;sleep 10;done"
efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c
[root@node01 ~]# docker logs centos_v1
hello docker
hello docker
[root@node01 ~]# docker exec -it centos_v1 /bin/bash
[root@efcd72ea15a7 /]# ps
PID TTY TIME CMD
22 pts/1 00:00:00 bash
36 pts/1 00:00:00 ps
# 退出时执行 exit
[root@efcd72ea15a7 /]# exit
exit
[root@node01 ~]# docker inspect centos_v1
[
{
"Id": "efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c",
"Created": "2021-06-13T09:51:13.330140921Z",
"Path": "/bin/bash",
"Args": [
"-c",
"while true;do echo hello docker;sleep 10;done"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 26148,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-06-13T09:51:14.040711318Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55",
"ResolvConfPath": "/docker/containers/efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c/resolv.conf",
"HostnamePath": "/docker/containers/efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c/hostname",
"HostsPath": "/docker/containers/efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c/hosts",
"LogPath": "/docker/containers/efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c/efcd72ea15a7e1ca8c17245d0ed41c7e331d646a488e3c8ef5a3d8fd7490dc3c-json.log",
"Name": "/centos_v1",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {
}
},
"NetworkMode": "default",
"PortBindings": {
},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/docker/overlay2/119adbf51607bfc6fdfc8f30081b9263dc961d09400f1695527eb6b728b2209b-init/diff:/docker/overlay2/2386f591a659192e7d79caae80c3eb520db3d1120fbcd7970d9d6eb8fab10507/diff",
"MergedDir": "/docker/overlay2/119adbf51607bfc6fdfc8f30081b9263dc961d09400f1695527eb6b728b2209b/merged",
"UpperDir": "/docker/overlay2/119adbf51607bfc6fdfc8f30081b9263dc961d09400f1695527eb6b728b2209b/diff",
"WorkDir": "/docker/overlay2/119adbf51607bfc6fdfc8f30081b9263dc961d09400f1695527eb6b728b2209b/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "efcd72ea15a7",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": true,
"OpenStdin": true,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash",
"-c",
"while true;do echo hello docker;sleep 10;done"
],
"Image": "centos:latest",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20201204",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS"
}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "0f3c7ccaebc706fb06f611ce07ce5c9a68c9ace1784a03af672f28bd3cc611d1",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
},
"SandboxKey": "/var/run/docker/netns/0f3c7ccaebc7",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "ee962448e879bc5b001e3d77c04d0fa05c94aa6c9120e8e42b0705fab44c898f",
"Gateway": "172.1.1.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.1.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:01:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "659b6c5dab2c8a0fd5058534fc195828865fc12d6e0388d47bf2a12118890998",
"EndpointID": "ee962448e879bc5b001e3d77c04d0fa05c94aa6c9120e8e42b0705fab44c898f",
"Gateway": "172.1.1.1",
"IPAddress": "172.1.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:01:00:02",
"DriverOpts": null
}
}
}
}
]
假设我们运行了一个容器,并完成了修改,想要将其保存为一个镜像,在没有聊到dockerfile前,可以使用commit来实现
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
efcd72ea15a7 centos:latest "/bin/bash -c 'while…" 6 minutes ago Up 6 minutes centos_v1
3b1157fa7e1b centos:latest "/bin/bash" 10 minutes ago Up 10 minutes sharp_shaw
[root@node01 ~]# docker commit efcd72ea15a7 centos_while:v1
sha256:d11182ec9f08c11e57151f5005e6200432faf9da5563114fd9a6b7c7c0859af8
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos_while v1 d11182ec9f08 5 seconds ago 209MB
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
efcd72ea15a7 centos:latest "/bin/bash -c 'while…" 9 minutes ago Up 9 minutes centos_v1
3b1157fa7e1b centos:latest "/bin/bash" 13 minutes ago Up 13 minutes sharp_shaw
[root@node01 ~]# docker stop efcd72ea15a7
efcd72ea15a7
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b1157fa7e1b centos:latest "/bin/bash" 13 minutes ago Up 13 minutes sharp_shaw
[root@node01 ~]# docker start efcd72ea15a7
efcd72ea15a7
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
efcd72ea15a7 centos:latest "/bin/bash -c 'while…" 10 minutes ago Up 22 seconds centos_v1
3b1157fa7e1b centos:latest "/bin/bash" 14 minutes ago Up 14 minutes sharp_shaw
先停后删
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
efcd72ea15a7 centos:latest "/bin/bash -c 'while…" 10 minutes ago Up 22 seconds centos_v1
3b1157fa7e1b centos:latest "/bin/bash" 14 minutes ago Up 14 minutes sharp_shaw
[root@node01 ~]# docker stop efcd
efcd
[root@node01 ~]# docker rm efcd
efcd
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b1157fa7e1b centos:latest "/bin/bash" 15 minutes ago Up 15 minutes sharp_shaw
如果容器已经运行起来,不便于重新运行一个新容器的情况下更新参数,可以使用docker update,但不是每一个参数都可以更新
# 查看下哪些参数可以被更新
[root@node01 ~]# docker update --help
Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]
Update configuration of one or more containers
Options:
--blkio-weight uint16 Block IO (relative weight), between 10 and 1000,
or 0 to disable (default 0)
--cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
--cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
--cpu-rt-period int Limit the CPU real-time period in microseconds
--cpu-rt-runtime int Limit the CPU real-time runtime in microseconds
-c, --cpu-shares int CPU shares (relative weight)
--cpus decimal Number of CPUs
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
--kernel-memory bytes Kernel memory limit
-m, --memory bytes Memory limit
--memory-reservation bytes Memory soft limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1' to
enable unlimited swap
--pids-limit int Tune container pids limit (set -1 for unlimited)
--restart string Restart policy to apply when a container exits
# 简单的解释
--blkio-weight 阻塞IO (相对权重),介于10到1000之间,0表示禁用(默认禁止)
--cpu-period 限制CPU CFS(完全公平的调度程序)期限
--cpu-quota 限制CPU CFS(完全公平的调度程序)配额
--cpu-rt-period API 1.25+,将CPU实时时间限制为微秒
--cpu-rt-runtime API 1.25+,将CPU实时运行时间限制为微秒
--cpu-shares, -c CPU份额(相对权重)
--cpus API 1.29+,CPU数量
--cpuset-cpus 允许执行的CPU(0-3,0,1)
--cpuset-mem 允许执行的MEM(0-3,0,1)
--kernel-memory 内核内存限制
--memory-swap 交换限制等于内存加交换,“-1”以启用无限交换
--memory-reservatio 内存软限制
--memory, -m 内存限制
--pids-limit API 1.40+,调节容器pids限制(-1表示无限制)
--restart 容器退出时重新启动策略以应用
# 更新下容器使其内存最高为 256M
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3b1157fa7e1b centos:latest "/bin/bash" 2 hours ago Up 2 hours sharp_shaw
[root@node01 ~]# docker update -m 256M --memory-swap -1 3b11 # --momory-swap -1是因为docker 默认没有启用memory-swap交换内存,直接设置了内存问题会出问题,也就是说宿主 swap 支持使用多少则容器即可使用多少,将memory-swap 设置值为 -1,表示容器程序使用内存受限,而 swap 空间使用不受限制
[root@node01 ~]# docker inspect 3b11|grep -Ei "\"memory\""
"Memory": 268435456, # 已经生效
存放镜像文件的仓库,我们这里不演示公共仓库,如果想要使用公共仓库,直接打开公共仓库的页面,都会有配置提示,我们本文只演示本地仓库
docker使用Registry(注册中心)来保存镜像,常见的registry有公有私有两种,私有最常见的封装方案是 harbor
综上,这里先显示 harbor镜像仓库的搭建(作为私有仓库),然后再来演示仓库的登入登出等
harbor仓库直接运行于docker中,采用docker-compose部署,故部署也是极为简单
harbor的组件:
1、Proxy:反向代理工具
2、Registry:负责存储docker镜像,处理上传/下载命令。对用户进行访问控制,它指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token,registry会通过公钥对token进行解密验证。
3、Core service:Harbor的核心功能:
4、Database:提供数据库服务,存储用户权限,审计日志,docker image分组信息等数据
5、Log collector:为了帮助监控harbor运行,复责收集其他组件的log,供日后进行分析
harbor主机采用 192.168.1.110来安装,关于基础环境初始化及yum源此处不再演示,参照上文即可
浏览器访问:https://github.com/goharbor/harbor/releases 进行下载
# 创建一些目录,用于后续使用
[root@harbor ~]# mkdir /data/app -p
[root@harbor ~]# mkdir /data/docker
[root@harbor ~]# mkdir /data/harbor
# 安装一下docker
[root@harbor ~]# yum -y install docker-ce
# 配置docker
[root@harbor ~]# mkdir /etc/docker
[root@harbor ~]# vim /etc/docker/daemon.json
{
"data-root": "/data/docker",
"bip": "172.1.1.1/16",
"storage-driver": "overlay2",
"registry-mirrors": ["https://xxxxxxxxxxxxxxxx.mirror.aliyuncs.com"]
}
[root@harbor ~]# systemctl start docker
[root@harbor ~]# systemctl enable docker
# 下载harbor
[root@harbor /]# cd /data/app/
[root@harbor app]# wget https://github.com/goharbor/harbor/releases/download/v2.2.2/harbor-offline-installer-v2.2.2.tgz
harbor使用docker-compose部署,关于docker-compose的使用,后面再聊,现在先来安装下(可以直接下载,也可以使用 # pip install docker-compose -i http://mirrors.aliyun.com/pypi/simple/
–trusted-host mirrors.aliyun.com)
docker-compose下载地址:https://github.com/docker/compose/releases/
# 下载
[root@node01 app]# wget https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64
# 给予执行权限
[root@harbor app]# cp docker-compose-Linux-x86_64 /usr/bin/docker-compose
[root@harbor app]# chmod 744 /usr/bin/docker-compose
[root@harbor app]# tar xf harbor-offline-installer-v2.2.2.tgz
[root@harbor app]# cd harbor
harbor/ harbor-offline-installer-v2.2.2.tgz
[root@harbor app]# cd harbor
[root@harbor harbor]# ls
common.sh harbor.v2.2.2.tar.gz harbor.yml.tmpl install.sh LICENSE prepare
# 修改配置文件
[root@harbor harbor]# cp -a harbor.yml.tmpl harbor.yml
[root@harbor harbor]# vim harbor.yml
hostname: harbor.local.top # 本地IP或者域名,这里使用域名,但我们没有DNS,故此等下做下hosts解析
harbor_admin_password: 123456 # harbor管理员密码
database:
password: 123456 # 数据库密码
data_volume: /data/harbor # 存储目录
# 注释掉https
# https related config
#https:
# # https port for harbor, default is 443
# port: 443
# # The path of cert and key files for nginx
# certificate: /your/certificate/path
# private_key: /your/private/key/path
# 做一下hosts解析,所有节点都要做,包括node1 node2
[root@harbor harbor]# echo "192.168.1.101 node01" >> /etc/hosts
[root@harbor harbor]# echo "192.168.1.102 node02" >> /etc/hosts
[root@harbor harbor]# echo "192.168.1.110 harbor" >> /etc/hosts
[root@harbor harbor]# echo "192.168.1.110 harbor.local.top" >> /etc/hosts
# 安装
[root@harbor harbor]# ./install.sh
[Step 0]: checking if docker is installed ...
Note: docker version: 20.10.7 # 成功读取docker版本
[Step 1]: checking docker-compose is installed ...
Note: docker-compose version: 1.29.2 # 成功读取docker-compose版本
......
[Step 5]: starting Harbor ...
Creating network "harbor_harbor" with the default driver
Creating harbor-log ... done
Creating registry ... done
Creating harbor-portal ... done
Creating harbor-db ... done
Creating registryctl ... done
Creating redis ... done
Creating harbor-core ... done
Creating nginx ... done
Creating harbor-jobservice ... done
✔ ----Harbor has been installed and started successfully.----
# 查看启动了哪些容器
[root@harbor harbor]# docker-compose ps # 注意docker-compose命令,只能于docker-compose项目目录下执行
Name Command State Ports
-------------------------------------------------------------------------------------------
harbor-core /harbor/entrypoint.sh Up (healthy)
harbor-db /docker-entrypoint.sh Up (healthy)
harbor-jobservice /harbor/entrypoint.sh Up (healthy)
harbor-log /bin/sh -c /usr/local/bin/ Up (healthy) 127.0.0.1:1514->10514/tcp
...
harbor-portal nginx -g daemon off; Up (healthy)
nginx nginx -g daemon off; Up (healthy) 0.0.0.0:80->8080/tcp,:::80
->8080/tcp
redis redis-server Up (healthy)
/etc/redis.conf
registry /home/harbor/entrypoint.sh Up (healthy)
registryctl /home/harbor/start.sh Up (healthy)
[root@harbor harbor]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be460fb4cb9b goharbor/harbor-jobservice:v2.2.2 "/harbor/entrypoint.…" About a minute ago Up About a minute (healthy) harbor-jobservice
9de4a1ca6be0 goharbor/nginx-photon:v2.2.2 "nginx -g 'daemon of…" About a minute ago Up About a minute (healthy) 0.0.0.0:80->8080/tcp, :::80->8080/tcp nginx
91ce9201ea03 goharbor/harbor-core:v2.2.2 "/harbor/entrypoint.…" 2 minutes ago Up About a minute (healthy) harbor-core
abe2e68cee15 goharbor/redis-photon:v2.2.2 "redis-server /etc/r…" 2 minutes ago Up 2 minutes (healthy) redis
96fdb49b29d9 goharbor/harbor-registryctl:v2.2.2 "/home/harbor/start.…" 2 minutes ago Up About a minute (healthy) registryctl
d978e8fc6d75 goharbor/harbor-portal:v2.2.2 "nginx -g 'daemon of…" 2 minutes ago Up 2 minutes (healthy) harbor-portal
55c2bbadcc3e goharbor/registry-photon:v2.2.2 "/home/harbor/entryp…" 2 minutes ago Up 2 minutes (healthy) registry
0bb8c40cea5c goharbor/harbor-db:v2.2.2 "/docker-entrypoint.…" 2 minutes ago Up 2 minutes (healthy) harbor-db
fd34d2af91e8 goharbor/harbor-log:v2.2.2 "/bin/sh -c /usr/loc…" 2 minutes ago Up 2 minutes (healthy) 127.0.0.1:1514->10514/tcp
因为都是图形化的,所以不多赘述,只说下怎么创建仓库
harbor的访问,使用浏览器输入harbor节点的IP:port或者域名即可
创建一个私有仓库(私有项目)
# node1节点先确认做一下hosts解析
[root@node01 ~]# echo "192.168.1.101 node01" >> /etc/hosts
[root@node01 ~]# echo "192.168.1.102 node02" >> /etc/hosts
[root@node01 ~]# echo "192.168.1.110 harbor" >> /etc/hosts
[root@node01 ~]# echo "192.168.1.110 harbor.local.top" >> /etc/hosts
# 登录私有仓库
因为我们这里没有使用https进行通信,所以还需要给docker配置下http不安全通信
[root@node01 ~]# vim /etc/docker/daemon.json
{
# 追加
"insecure-registries": ["harbor.local.top"]
}
[root@node01 ~]# systemctl daemon-reload
[root@node01 ~]# systemctl restart docker
# 登录私有仓库
[root@node01 ~]# docker login harbor.local.top
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See # 这句警告是说你的密码会不安全的存储在/root/.docker/config.json中。配置凭据助手可以去掉这个警告
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
# 推送镜像到私有仓库
在推送前,需要先给本地的镜像打标签,以符合harbor中的项目路径
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos_while v1 d11182ec9f08 3 hours ago 209MB
......
[root@node01 ~]# docker tag centos_while:v1 harbor.local.top/images/centos_while:v1
推送
[root@node01 ~]# docker push harbor.local.top/images/centos_while:v1
The push refers to repository [harbor.local.top/images/centos_while]
6ecc5281cd8d: Pushed
2653d992f4ef: Pushing 85.67MB/209.3MB
[root@node01 ~]# docker logout harbor.local.top
Removing login credentials for harbor.local.top
接下来我们来实际运行一个应用容器
以nginx为例
运行容器前,除非是不重要的测试环境,否则一定要考虑好该容器的 端口 存储 等等信息后再运行
如果是官方镜像,那么这个容器有哪些参数可以被配置,都会写的很清晰,比如前往dockerhub网站,阅读容器的文档
此处,我以nginx容器为例
打开 https://registry.hub.docker.com/_/nginx
如上,可以看到官方会给出很多运行参数的示例
此处规划的参数如下:
-p 8001:80 # -p 映射端口,8001为宿主机端口,80为容器内端口
-v /data/docker/nginx/html:/usr/share/nginx/html # -v 挂载卷,/data/docker/nginx/html是宿主机目录,/usr/share/nginx/html是容器内目录,这个挂载用于实现html目录的挂载
--name 8001 # 设置容器名
-v /data/docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # -v不仅可以挂载目录,也可以挂载单个文件,这里就是挂载的配置文件,ro是只读,默认是rw
--restart=always # 重启策略为任何状态自动重启
既然做好了规划,那么下面就把规划的内容准备好
# 创建挂载卷的宿主机目录
[root@node01 ~]# mkdir /data/docker/nginx/html -p
# 准备html目录下的index.html测试文件
[root@node01 ~]# echo "nginx docker test" >> /data/docker/nginx/html/index.html
# 准备要被挂载进去的nginx.conf配置文件
[root@node01 ~]# vim /data/docker/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}
# 拉取镜像
[root@node01 ~]# docker pull nginx
# 运行
[root@node01 ~]# docker run -d --name="nginx_8001" -v /data/docker/nginx/html:/usr/share/nginx/html -v /data/docker/nginx/nginx.conf:/etc/nginx/nginx.conf -p 8001:80 --restart=always nginx:latest
c410430dc13d7d27a2ed7b41ee8f40ddedac54a2afe0d753d044d6718836643a
# 查看启动后的状态
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c410430dc13d nginx:latest "/docker-entrypoint.…" 19 seconds ago Up 18 seconds 0.0.0.0:8001->80/tcp, :::8001->80/tcp nginx_8001
# 查看宿主机端口
[root@node01 ~]# ss -naltp|grep 8001
LISTEN 0 128 *:8001 *:* users:(("docker-proxy",pid=93384,fd=4))
LISTEN 0 128 [::]:8001 [::]:* users:(("docker-proxy",pid=93389,fd=4))
测试访问
[root@harbor harbor]# curl node01:8001
nginx docker test
测试自动重启策略
[root@node01 ~]# systemctl restart docker
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c410430dc13d nginx:latest "/docker-entrypoint.…" 2 minutes ago Up 1 second 0.0.0.0:8001->80/tcp, :::8001->80/tcp nginx_8001
这里简单介绍下数据卷的挂载
在上面我们采用-V参数,挂载了数据存储目录到容器内,实际上这就是数据卷,数据卷主要解决的是数据持久化到宿主机且宿主机与容器间数据同步
挂载数据卷做持久化在绝大多数时候都是是必要的,毕竟容器可以被随意删除,一旦容器崩溃或者被删除掉,数据就丢失了。
另外多提一句,一般情况下,不建议在容器里运行有状态服务,比如mysql redis等,主要也是数据丢失的风险
docker本地共有四种网络模型,分别为 bridge,host,none,container
# 查看docker 网络
[root@node01 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
95ab2996b951 bridge bridge local
ff9ddd83a44a host host local
3ab59d75c16c none null local
# 查看容器使用的网络模式
[root@node01 ~]# docker inspect c410
# 创建一个网络
# 以创建一个新的briage网络 bridge_test1 为例
[root@node01 ~]# docker network create -d bridge --subnet "10.1.1.1/24" --gateway="10.1.1.254" briage_test1
2bdd140c3dfb69e0942d93a8416628d4c0cdd77b1e2437ff446e1b658aaaf0bb
[root@node01 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
2bdd140c3dfb briage_test1 bridge local 新创建的网络
95ab2996b951 bridge bridge local
ff9ddd83a44a host host local
3ab59d75c16c none null local
# 删除一个网络
[root@node01 ~]# docker network rm briage_test1
briage_test1
[root@node01 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
95ab2996b951 bridge bridge local
ff9ddd83a44a host host local
3ab59d75c16c none null local
默认情况下,docker即使用bridge桥接网络,这个桥接和我们日常理解的桥接不同,它更像是我们在虚拟机中使用的NAT网络。
当宿主机存在多块网卡时,可以通过brctl命令将指定的物理网卡桥接到docker0上
当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。
[root@node01 ~]# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.1.1.1 netmask 255.255.0.0 broadcast 172.1.255.255
inet6 fe80::42:4aff:fece:f9d1 prefixlen 64 scopeid 0x20<link>
ether 02:42:4a:ce:f9:d1 txqueuelen 0 (Ethernet)
RX packets 12 bytes 1100 (1.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 17 bytes 1267 (1.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node01 ~]# route -n | grep docker0
172.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
从docker0子网中分配一个IP给容器使用,并设置docker的IP地址为容器的默认网关,在主机上创建一对虚拟网卡veth pair设备,docker将veth pair设备的一端放在容器中,并以eth0命名,一端放在宿主机中,以vethxxx命名,并且将这个网络加入到docker0网桥中,可以通过 # brctl show 命令查看(没有brctl安装下 # yum install bridge-utils -y)
# 起个容器
[root@node01 ~]# docker run -itd --name="centos_test1" centos:7.9.2009 /bin/bash
55124e99e88f2da9f61a927eb301ef43e043eb9eb862579d894ce8903a31e2e8
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55124e99e88f centos:7.9.2009 "/bin/bash" 6 seconds ago Up 6 seconds centos_test1
# 查看容器内网卡名
[root@node01 ~]# docker exec -it 5512 /bin/bash
[root@55124e99e88f /]# yum -y install net-tools
[root@55124e99e88f /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.1.0.2 netmask 255.255.0.0 broadcast 172.1.255.255
ether 02:42:ac:01:00:02 txqueuelen 0 (Ethernet)
RX packets 3877 bytes 16524721 (15.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3223 bytes 178107 (173.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
......
# 查看宿主机内网卡名
[root@node01 ~]# ip a
17: veth48645da@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 0a:45:b9:c9:ea:2c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::845:b9ff:fec9:ea2c/64 scope link
valid_lft forever preferred_lft forever
......
# 网桥控制器查看
[root@node01 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02424acef9d1 no veth48645da
bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用 # iptables -t nat -vnL查看。
如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
启动容器时使用host网络
–net=host 参数
以nginx镜像为例,nginx镜像默认使用80端口,我们来验证下
# 查看下宿主机80端口没有冲突
[root@node01 ~]# ss -naltp|grep 80
# 使用 --net=host 来指定使用host网络模式
[root@node01 ~]# docker run -d --net=host --name="nginx_host_test1" nginx:latest
b908023b773f1a5163cc8cf91864c3684f546e937159396fdaa0e9a81d39b29b
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b908023b773f nginx:latest "/docker-entrypoint.…" 5 seconds ago Up 4 seconds nginx_host_test1
# 再来查看下宿主机中的端口
[root@node01 ~]# ss -naltp|grep 80
LISTEN 0 128 *:80 *:* users:(("nginx",pid=107305,fd=7),("nginx",pid=107304,fd=7),("nginx",pid=107262,fd=7))
LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=107305,fd=8),("nginx",pid=107304,fd=8),("nginx",pid=107262,fd=8))
可以看到,80端口直接出现在了宿主机中
# 直接访问下
[root@node01 ~]# curl localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
......
用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
通过 --net=none参数指定
# 运行时指定--net=none
[root@node01 ~]# docker run -itd --net=none --name="centos7.9_none_test1" centos:7.9.2009 /bin/bash
e81b1e6ca20d9d3469dcfe4876e7828099167ba5731a7fcb60c5d0b0d686293a
[root@node01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e81b1e6ca20d centos:7.9.2009 "/bin/bash" 4 seconds ago Up 2 seconds centos7.9_none_test1
# 查看下网络设备
[root@node01 ~]# docker exec -it e81b /bin/bash
[root@e81b1e6ca20d /]# cat /proc/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
可以看到,只有一个lo网络
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。
–net=container:被依附的容器名
# 先启动一个新的容器,命名为 centos_container_test1
[root@node01 ~]# docker run -itd --name centos_container_test1 centos:7.9.2009 /bin/bash
49595449fb50b82625af2c08745e2597853af450a98973b477020bf442f61c9c
# 在启动一个容器,启动时指定网络为container并依附到 centos_container_test1
[root@node01 ~]# docker run -itd --name centos_container_test2 --net=container:centos_container_test1 centos:7.9.2009 /bin/bash
ca27254e32512c5c5ec095f590d5c627c91ff08a3c8a00e4bf0628479ba549c9
# 查看网络是否相同
[root@node01 ~]# docker exec -it centos_container_test1 /bin/bash
[root@49595449fb50 /]# yum -y install net-tools
[root@49595449fb50 /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.1.0.3 netmask 255.255.0.0 broadcast 172.1.255.255
ether 02:42:ac:01:00:03 txqueuelen 0 (Ethernet)
RX packets 4075 bytes 16535781 (15.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3213 bytes 177825 (173.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
......
[root@node01 ~]# docker exec -it centos_container_test2 /bin/bash
[root@49595449fb50 /]# yum -y install net-tools
[root@49595449fb50 /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.1.0.3 netmask 255.255.0.0 broadcast 172.1.255.255
ether 02:42:ac:01:00:03 txqueuelen 0 (Ethernet)
RX packets 6926 bytes 33005634 (31.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5632 bytes 312908 (305.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
......
前面实验开启了非常多的容器,怎么一次全停止并删除
# 停
[root@node01 ~]# docker stop $(docker ps -q)
ca27254e3251
49595449fb50
e81b1e6ca20d
b908023b773f
55124e99e88f
c410430dc13d
# 删
[root@node01 ~]# docker rm $(docker ps -aq)
ca27254e3251
49595449fb50
e81b1e6ca20d
b908023b773f
55124e99e88f
c410430dc13d
3b1157fa7e1b
36a3d9354ba4
[root@node01 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
构建镜像除了前面提到的docker commit,还有dockerfile。实际真正使用的也是dockerfile
Dockerfile把构建镜像的步骤都写出来,然后按顺序执行实现自动构建镜像,就像ansible的playbook类似,也类似于使用pom去构建maven项目…
dockerfile的组成模块
关键字 | 模块 |
---|---|
FROM | 基础镜像信息 |
MAINTAINER | 维护者信息 |
RUN COPY ADD USER WORKDIR VOLUME EXPOSE… | 容器操作 |
CMD ENTRYPOINT | 启动操作 |
指定以哪个镜像为基础镜像
# 比如以centos为基础镜像
FROM centos:7.9.2009
指定镜像维护者信息
# 比如指定维护者及邮件
MAINTAINER "systemctl529_yu" "[email protected]"
用于在构建镜像中执行命令,比如安装一些软件、配置一些基础环境,可使用\来换行或者用 && 连续执行,尽量减少多行RUN*(一个RUN一层)*
# 两种语法:
1. shell格式
RUN yum -y install nginx && echo "test web" >> /usr/share/nginx/html/index.html
RUN echo "hello docker" \
/usr/share/nginx/html/index.html
2. exec格式
RUN ["命令", "参数1", "参数2"]
RUN ["yum","-y","install","nginx"]
将主机的文件复制到镜像内,如果目的位置不存在,Docker会自动创建所有需要的目录结构,但是它只是单纯的复制,并不会去做文件提取和解压工作。
需要复制的目录一定要放在Dockerfile文件的同级目录下
# 拷贝配置文件到容器中
COPY nginx.conf /etc/nginx
将主机的文件复制到镜像中,跟COPY一样,限制条件和使用方式都一样,也是需要在dockerfile的同级目录中。
ADD会对压缩文件(tar, gzip, bzip2, etc)做提取和解压操作
# 拷贝html.tar到容器中
ADD HTML.tar /usr/share/nginx/html
暴露镜像的端口供主机做映射,启动镜像时,使用-P参数来将镜像端口与宿主机的随机端口做映射。(可指定多个)
docker run时,还需 -p 参数指定内外端口
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射 EXPOSE 的端口
EXPOSE 80 81 82 ...
ENV指令用于指定一个环境变量
当使用生成的镜像运行容器时,使用 ENV
设置的环境变量将持久存在于容器内,如果仅仅要在构建过程中使用变量,可以 RUN MY_NAME=“systemctl529_yu”,或者用ARG MY_NAME="systemctl529_yu"
# ENV 或者 ENV =
ENV JAVA_HOME /usr/local/openjdk/bin
ENV MY_NAME="systemctl529_yu" MY_JOB="OPS"
在构建镜像时,指定镜像的工作目录,之后的命令都是基于此工作目录,如果不存在,则会创建目录
WORKDIR /usr/share/nginx/html
RUN echo 'hello docker' > text.txt # 这样就会在容器的/usr/share/nginx/html/text.txt文件写入 “hello docker”
为他人做嫁衣,做基础镜像使用
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。比如我们需要一个java镜像跑jar包,每次都创建一个jre环境太麻烦,所以就可以先创建个基础镜像,然后每次放不同jar进去做新的镜像
语法格式为 :ONBUILD <其它指令>
# 创建基础镜像image1
FROM openjdk
RUN mkdir /APP
ONBUILD COPY xxxxx.jar /APP
WORKDIR /APP
CMD java -jar xxxxx.jar
# 基于基础镜像1创建镜像image2
FROM image1 # 似的,没看错,就这一行,其会调用image1中的ONBUILD
指定该镜像以哪个用户去执行(比如以hadoop用户执行hadoop)
如果设置了容器以XXXXX用户去运行,那么RUN,CMD和ENTRYPOINT都会以这个用户去运行
镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户
USER XXXX
用来向基于镜像创建的容器添加卷。比如你可以将mongodb镜像中存储数据的data文件指定为宿主机的某个文件。(容器内部建议不要存储任何数据),如果只指定挂载点,docker宿主机映射的目录会自动生成。
# VOLUME 宿主机目录 容器内目录
或者
# VOLUME 容器目录
VOLUME /data/nginx/html /usr/share/nginx/html
容器启动时需要执行的命令
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令
每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行,同时CMD可以被docker run 时指定的运行命令覆盖
# 两种语法
1. exec写法
CMD ["命令","参数1","参数2"]
CMD ["/bin/bash"]
2. shell写法
CMD 命令 参数1 参数2
CMD /bin/bash
用法和功能与CMD一致
ENTRYPOINT指定的命令不会被docker run覆盖,但会把docker run后的命令当做 ENTRYPOINT指定命令的参数,如果dockerfile中同时有CMD和ENTRYPOINT,那么CMD也会被ENTRYPOINT当做执行命令的参数
# 两种语法
1. exec写法
ENTRYPOINT ["命令","参数1","参数2"]
ENTRYPOINT ["/bin/bash"]
2. shell写法
ENTRYPOINT 命令 参数1 参数2
ENTRYPOINT /bin/bash
build创建镜像时,需要在命令后写一个 . 这个 . 代表当前目录上下文(当前目录及当前目录下的子目录)
[root@node01 ~]# mkdir /data/dockerfile -p
[root@node01 ~]# cd /data/docker
[root@node01 docker]# mkdir nginx_dockerfile
[root@node01 docker]# cd nginx_dockerfile/
[root@node01 nginx_dockerfile]#
# 以centos为基础镜像,写一个nginx的容器
[root@node01 nginx_dockerfile]# vim nginx_dockerfile
FROM centos:latest
MAINTAINER "systemctl529_yu" "xxxxxxx.163.com"
RUN yum -y install nginx
COPY index.html /usr/share/nginx/html
COPY nginx.conf /etc/nginx
EXPOSE 80
CMD nginx -g "daemon off;"
# 准备好dockerfile中定义的文件
[root@node01 nginx_dockerfile]# echo "dockerfile_nginx_test1" >> index.html
[root@node01 nginx_dockerfile]# vim nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
# build构建镜像
[root@node01 nginx_dockerfile]# docker build -f nginx_dockerfile -t nginx_dockerfile:test1 .
Sending build context to Docker daemon 4.608kB
Step 1/7 : FROM centos:latest
---> 300e315adb2f
Step 2/7 : MAINTAINER "systemctl529_yu" "xxxxxxx.163.com"
---> Running in 00ba4060520f
Removing intermediate container 00ba4060520f
---> 1b3338ca9f72
Step 3/7 : RUN yum -y install nginx
......
Step 4/7 : COPY index.html /usr/share/nginx/html
---> 97379c724a8d
Step 5/7 : COPY nginx.conf /etc/nginx
---> 23b118c3271d
Step 6/7 : EXPOSE 80
---> Running in 4b14485d0bcd
Removing intermediate container 4b14485d0bcd
---> f88b7595fefa
Step 7/7 : CMD nginx -g "daemon off;"
---> Running in 1df3be4f8e69
Removing intermediate container 1df3be4f8e69
---> 3a7c2ca9c867
Successfully built 3a7c2ca9c867
Successfully tagged nginx_dockerfile:test1
[root@node01 nginx_dockerfile]# docker images | grep nginx_dockerfile
nginx_dockerfile test1 3a7c2ca9c867 About a minute ago 313MB
# 运行个容器试试
[root@node01 nginx_dockerfile]# docker run -d --name="nginx_dockerfile_test1_8001" -p 8001:80 nginx_dockerfile:test1
9c483dccd5e657006d46b60dc04422eb138b8050552b2358c1889a345392a477
[root@node01 nginx_dockerfile]# curl localhost:8001
dockerfile_nginx_test1
[root@node01 docker]# mkdir /data/docker/tomcat_dockerfile
[root@node01 docker]# cd /data/docker/tomcat_dockerfile/
[root@node01 tomcat_dockerfile]# vim tomcat_dockerfile
# 这里没有war包演示,所以就用tomcat默认页面了,实际使用,可以使用 RUN rm -rf /usr/local/tomcat/webapps/*删除掉自带的项目,然后CPOY进WAR包
FROM adoptopenjdk/openjdk8:ubi
MAINTAINER "systemctl529_yu" "xxxxxxx.163.com"
ADD apache-tomcat-9.0.14.tar.gz /usr/local
EXPOSE 8080
CMD /usr/local/apache-tomcat-9.0.14/bin/catalina.sh run
# 下载好tomcat到当前目录
[root@node01 tomcat_dockerfile]# ls
apache-tomcat-9.0.14.tar.gz tomcat_dockerfile
# build构建
[root@node01 tomcat_dockerfile]# docker build -f tomcat_dockerfile -t tomcat_dockerfile:test1 .
至于jar包的,更简单,比如
FROM adoptopenjdk/openjdk8:ubi
RUN mkdir /opt/app
COPY xxxxxxx.jar /opt/app
CMD ["java", "-jar", "/opt/app/xxxxxxx.jar"]