上传文件至 'Docker-MD'

This commit is contained in:
diandian 2023-03-26 12:29:30 +08:00
parent 94d2548099
commit 2dda724ffd
4 changed files with 1799 additions and 0 deletions

434
Docker-MD/Docker入门.md Normal file
View File

@ -0,0 +1,434 @@
<h1><center>初始Docker</center></h1>
**作者:行癫(盗版必究)**
------
## 一:容器简介
#### 1.Docker之logo
<img src="https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/image-20220613223124564.png" alt="image-20220613223124564" style="zoom:50%;" />
容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用"装"起来的技术。这样,应用与应用之间,就因为有了边界而不至于相互干扰;而被装进集装箱的应用,也可以被方便地搬来搬去,这其实是 PaaS 最理想的状态。
#### 2.云计算服务类型
基础设施即服务IaaS
平台即服务PaaS
软件即服务SaaS
注意:
IAAS指把IT基础设施作为一种服务通过网络对外提供并根据用户对资源的实际使用量或占用量进行计费的一种服务模式
PaaS为开发人员提供了一个框架使他们可以基于它创建自定义应用程序。所有服务器存储和网络都可以由企业或第三方提供商进行管理而开发人员可以负责应用程序的管理
SaaS提供商为企业搭建信息化所需要的所有网络基础设施及软件、硬件运作平台并负责所有前期的实施、后期的维护等一系列服务企业无需购买软硬件、建设机房、招聘IT人员即可通过互联网使用信息系统。就像打开自来水龙头就能用水一样企业根据实际需要向SaaS提供商租赁软件服务
#### 3.容器的本质
容器的本质是进程,容器就是未来云计算系统中的进程
#### 4.容器和虚拟化对比
容器是应用程序层的抽象将代码和依赖项打包在一起。多个容器可以在同一台计算机上运行并与其他容器共享OS内核每个容器在用户空间中作为隔离的进程运行。容器占用的空间少于VM可以处理更多的应用程序并且需要的VM和操作系统更少
<img src="https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/image-20220613223852295.png" alt="image-20220613223852295" style="zoom:50%;" />
#### 5.Docker基本概念
Docker系统有两个程序docker服务端和docker客户端
Docker服务端
是一个服务进程,管理着所有的容器
Docker客户端
是docker服务端的远程控制器可以用来控制docker的服务端进程
#### 6.Docker的优势
###### 交付物标准化
Docker是软件工程领域的"标准化"交付组件,最恰到好处的类比是"集装箱";集装箱将零散、不易搬运的大量物品封装成一个整体,集装箱更重要的意义在于它提供了一种通用的封装货物的标准,卡车、火车、货轮、桥吊等运输或搬运工具采用此标准,隧道、桥梁等也采用此标准。以集装箱为中心的标准化设计大大提高了物流体系的运行效率
注意:
传统的软件交付物包括:应用程序、依赖软件安装包、配置说明文档、安装文档、上线文档等非标准化组件
Docker的标准化交付物称为"镜像",它包含了应用程序及其所依赖的运行环境,大大简化了应用交付的模式
###### 一次构建,多次交付
类似于集装箱的"一次装箱,多次运输"Docker镜像可以做到"一次构建,多次交付"。当涉及到应用程序多副本部署或者应用程序迁移时更能体现Docker的价值
###### 应用隔离
集装箱可以有效做到货物之间的隔离使化学物品和食品可以堆砌在一起运输。Docker可以隔离不同应用程序之间的相互影响但是比虚拟机开销更小总之容器技术部署速度快开发、测试更敏捷提高系统利用率降低资源成本
#### 7.Docker核心组件
Docker 镜像 - Docker images
Docker 仓库 - Docker registeries
Docker 容器 - Docker containers
#### 8.Docker仓库
用来保存镜像可以理解为代码控制中的代码仓库。同样的Docker 仓库也有公有和私有的概念
公有的 Docker 仓库名字是 Docker Hub
###### 库registry
公有库:
Docker-hub Daocloud ali 网易蜂巢
私有库:
公司内部使用(自己部署)
###### 分类:
操作系统名称 centos ubuntu
应用名称 nginx tomcat mysql
###### Tag
表示镜像版本
#### 9.Docker镜像
Docker 镜像是 Docker 容器运行时的只读模板,每一个镜像由一系列的层 (layers) 组成
每一个镜像都可能依赖于由一个或多个下层的组成的另一个镜像,下层那个镜像是上层镜像的父镜像
###### 镜像名称:
仓库名称+镜像分类+tag名称(镜像版本)
###### 完整镜像名称:
docker.io/nginx:v1
docker.io/nginx:latest
daocloud.io/centos:6
###### 镜像ID
64位的id号
例如e6ea68648f0cd70c8d77c79e8cd4c17f63d587815afcf274909b591cb0e417ab
###### 基础镜像:
一个没有任何父镜像的镜像,谓之基础镜像
###### 注意:
Registry中镜像是通过Repository来组织的而每个Repository又包含了若干个Image。Registry包含一个或多个Repository
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/0.png)
#### 10.Docker容器
Docker 容器和文件夹很类似一个Docker容器包含了所有的某个应用运行所需要的环境
每一个 Docker 容器都是从 Docker 镜像创建的Docker 容器可以运行、开始、停止、移动和删除
每一个 Docker 容器都是独立和安全的应用平台Docker 容器是 Docker 的运行部分
## 二:容器安装部署
#### 1.Docker版本
Docker-ce
Docker-ee
#### 2.Docker安装
```shell
[root@xingdian ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 git
[root@xingdian ~]# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@xingdian ~]# yum install docker-ce -y
以上是安装最新版本,如果需要指定版本安装,见下面命令:
查看docker版本
[root@xingdian ~]# yum list docker-ce --showduplicates
指定版本安装:
[root@xingdian ~]# yum install docker-ce-20.10.2.ce -y
启动服务并做开机启动:
[root@xingdian ~]# systemctl enable docker
[root@xingdian ~]# systemctl start docker
```
#### 3.Docker查看
###### 查看安装版本:
```shell
[root@master ~]# docker -v
Docker version 20.10.16, build aa7e414
```
###### 查看docker运行状态
```shell
[root@master ~]# docker info
Client:
Context: default
Debug Mode: false
Plugins:
app: Docker App (Docker Inc., v0.9.1-beta3)
buildx: Docker Buildx (Docker Inc., v0.8.2-docker)
scan: Docker Scan (Docker Inc., v0.17.0)
Server:
Containers: 33
Running: 16
Paused: 0
Stopped: 17
Images: 12
Server Version: 20.10.16
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: 212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
runc version: v1.1.1-0-g52de29d
init version: de40ad0
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-1160.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.701GiB
Name: master
ID: VQQL:HVIY:NPF2:OPE6:SQK7:ZRGZ:RQVG:3XKX:MLMD:OYT5:OZKP:FRYW
Docker Root Dir: /var/lib/docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
192.168.18.230:80
127.0.0.0/8
Live Restore Enabled: false
```
## 三:国内镜像仓库的使用
#### 1.国内镜像站
https://www.daocloud.io
#### 2.国外镜像站
https://hub.docker.com
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/0-16551326599845.png)
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/0-16551326919458.png)
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/0-165513272390511.png)
#### 3.镜像加速器
使用 Docker 的时候,需要经常从官方获取镜像,但是由于显而易见的网络原因,拉取镜像的过程非常耗时,严重影响使用 Docker 的体验。因此 DaoCloud 推出了加速器工具解决这个难题,通过智能路由和缓存机制,极大提升了国内网络访问 Docker Hub 的速度,目前已经拥有了广泛的用户群体,并得到了 Docker 官方的大力推荐
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/0-165513277255814.png)
#### 4.登陆登出Docker Hub
登录到自己的Docker register需有Docker Hub的注册账号
```shell
[root@xingdian ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: 17343051369
Password:
Login Succeeded
logout Log out from a Docker registry
退出登录
# docker logout
Remove login credentials for https://index.docker.io/v1/
```
## 四Docker使用
#### 1.镜像操作
查看centos所有的镜像
```shell
[root@xingdian ~]# docker search centos
```
注意:
凡是镜像大于100的显示出来小于100的不显示
```shell
[root@xingdian ~]# docker search centos --filter=stars=100
OFFICIAL [OK]代表官方的
AUTOMATED [OK]代表完整的镜像
```
拉取镜像:
```shell
[root@xingdian ~]# docker pull centos
[root@xingdian ~]# docker pull daocloud.io/library/centos:7.8.2003
```
查看本地镜像:
```shell
[root@xingdian ~]# docker image list
[root@xingdian ~]# docker images
[root@xingdian ~]# docker image ls
```
查看镜像详情:
```shell
[root@xingdian ~]# docker image inspect 镜像id
```
删除镜像:
注意删除一个或多个多个之间用空格隔开可以使用镜像名称或id
```shell
[root@xingdian ~]# docker rmi daocloud.io/library/mysql
强制删除:--force
如果镜像正在被使用中可以使用--force强制删除
[root@xingdian ~]# docker rmi docker.io/ubuntu:latest --force
```
删除所有镜像:
```shell
[root@xingdian ~]# docker rmi $(docker images -q)
#-q查出所有id号
只查看所有镜像的id:
[root@xingdian ~]# docker images -q
```
#### 2.容器操作
启动容器并指定名为server并放后台运行
```shell
[root@xingdian ~]# docker run --name server -it -d centos:latest /bin/bash
```
使用镜像 nginx:latest以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data
```shell
[root@blackmed ~]# docker run -p 80:80 -v /data:/data -d nginx:latest
```
使用镜像nginx:latest以交互模式启动容器,容器内执行/bin/bash命令
```shell
[root@blackmed ~]# docker run -it nginx:latest /bin/bash
参数:
• -v文件映射格式为主机目录容器目录
• -d: 后台运行容器并返回容器ID
• -i: 以交互模式运行容器,通常与 -t 同时使用;
• -p: 端口映射,格式为:主机(宿主)端口:容器端口
• -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
• --name="nginx-lb": 为容器指定一个名称;
• --dns 8.8.8.8: 指定容器使用的DNS服务器默认和宿主一致
• --dns-search example.com: 指定容器DNS搜索域名默认和宿主一致
• -h "mars": 指定容器的hostname
• -e username="ritchie": 设置环境变量;
• --cpuset-cpus="0-2" or --cpuset-cpus="0,1,2": 绑定容器到指定CPU运行
--privileged 以特权模式运行
```
#### 3.查看容器
只查看运行状态的容器:
```shell
[root@xingdian ~]# docker ps
[root@xingdian ~]# docker ps -a
-a 查看所有容器
只查看所有容器id:
[root@xingdian ~]# docker ps -a -q
列出最近一次启动的容器
[root@xingdian ~]# docker ps -l
```
查看容器详细信息:
```shell
[root@xingdian ~]# docker inspect 1fbf6
[
{
"Id": "1fbf6d9c50217c0f630fec1f98c6647e38e2371af94f2860d34674eeffd42f84",
"Created": "2020-08-12T15:52:39.064893415Z",
"Path": "/docker-entrypoint.sh",
"Args": [
"nginx",
"-g",
"daemon off;"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
容器信息很多,这里只粘贴了一部分
```
#### 4.启动容器
```shell
[root@xingdian ~]# docker start name
```
#### 5.关闭容器
```shell
[root@xingdian ~]# docker stop name
退出不关闭:
快捷键ctrl +p+q
```
#### 6.删除容器
```shell
[root@xingdian ~]# docker rm 容器id或名称
要删除一个运行中的容器,添加 -f 参数
根据格式删除所有容器:
[root@xingdian ~]# docker rm $(docker ps -a -q)
```

371
Docker-MD/Docker应用.md Normal file
View File

@ -0,0 +1,371 @@
<h1><center>Docker应用</center></h1>
**作者:行癫(盗版必究)**
------
## 一:端口转发
容器172.16.0.2 5000
client----->eth0:10.18.45.197------->172.16.0.2:5000
5000
使用端口转发解决容器端口访问问题
-p:
创建应用容器的时候,一般会做端口映射,这样是为了让外部能够访问这些容器里的应用。可以用多个-p指定多个端口映射关系
mysql应用端口转发
```shell
查看本地地址:
[root@xingdian ~]# ip a
ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:0a:5b:8b brd ff:ff:ff:ff:ff:ff
inet 192.168.245.134/24 brd 192.168.245.255 scope global dynamic ens33
valid_lft 1444sec preferred_lft 1444sec
```
运行容器:使用-p作端口转发把本地3307转发到容器的3306其他参数需要查看发布容器的页面提示
```shell
[root@xingdian ~]# docker run --name mysql1 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123 daocloud.io/library/mysql
```
通过本地IP192.168.245.134的3307端口访问容器mysql1内的数据库出现如下提示恭喜你
```shell
[root@xingdian /]# mysql -u root -p123 -h 192.168.245.134 -P3307
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.18 MySQL Community Server (GPL)
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
```
## 二:部署私有仓库
仓库镜像Docker hub官方已提供容器镜像registry,用于搭建私有仓库
拉取镜像:
```shell
[root@xingdian ~]# docker pull daocloud.io/library/registry:latest
```
运行容器:
```shell
[root@xingdian ~]# docker run --restart=always -d -p 5000:5000 daocloud.io/library/registry
```
注:如果创建容器不成功,报错防火墙,解决方案如下
```shell
[root@xingdian ~]# systemctl stop firewalld
[root@xingdian ~]# systemctl restart docker
```
查看运行的容器:
```shell
[root@xingdian ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f444285bed8 daocloud.io/library/registry "/entrypoint.sh /etc/" 23 seconds ago Up 21 seconds 0.0.0.0:5000->5000/tcp elegant_rosalind
```
连接容器查看端口状态:
```shell
[root@xingdian ~]# docker exec -it  1f444285bed8  /bin/sh     //这里是sh 不是bash
/ # netstat -antpl //查看5000端口是否开启(容器内查看)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 :::5000 :::* LISTEN 1/registry
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node PID/Program name Path
```
在本机查看能否访问该私有仓库, 看看状态码是不是200
```shell
[root@xingdian registry]# curl -I 127.0.0.1:5000
HTTP/1.1 200 OK
Cache-Control: no-cache
Date: Thu, 08 Oct 2020 05:34:32 GMT
```
为了方便下载1个比较小的镜像,buysbox
```shell
[root@xingdian registry]# docker pull busybox
```
上传前必须给镜像打tag 注明ip和端口
```shell
[root@xingdian ~]# docker tag busybox  本机IP端口/busybox
```
这是直接从官方拉的镜像,很慢:
```shell
[root@xingdian ~]# docker tag busybox 192.168.245.136:5000/busybox
```
下面这个Mysql是我测试的第二个镜像从daocloud拉取的
```shell
[root@xingdian ~]# docker tag daocloud.io/library/mysql 192.168.245.136:5000/daocloud.io/library/mysql
```
注意:
tag后面可以使用镜像名称也可以使用id,我这里使用的镜像名称如果使用官方的镜像不需要加前缀但是daocloud.io的得加前缀
修改请求方式为http:
```shell
默认为https不改会报以下错误:
Get https://master.up.com:5000/v1/_ping: http: server gave HTTP response to HTTPS client
[root@xingdian ~]# vim /etc/docker/daemon.json
{ "insecure-registries":["192.168.245.136:5000"] }
重启docker:
[root@xingdian ~]# systemctl restart docker
```
上传镜像到私有仓库:
```shell
[root@xingdian ~]# docker push 192.168.245.136:5000/busybox
[root@xingdian ~]# docker push 192.168.245.136:5000/daocloud.io/library/mysql
```
查看私有仓库里的所有镜像:
```shell
[root@xingdian ~]# curl 192.168.245.130:5000/v2/_catalog
{"repositories":["busybox"]}
```
查看私有仓库里的镜像版本:
```shell
[root@docker ~]# curl 10.11.67.110:5000/v2/busybox/tags/list
{"name":"busybox","tags":["v1","v2"]}
```
```shell
[root@docker ~]# curl -XGET http://10.11.67.110:3000/v2/busybox/tags/list
{"name":"busybox","tags":["v1","v2"]}
查询镜像digest_hash删除命令里边要填写的 镜像digest_hash 就是 查询结果里边 Docker-Content-Digest: 后边的内容
[root@docker ~]# curl --header "Accept:application/vnd.docker.distribution.manifest.v2+json" -I -XGET http://10.11.67.110:3000/v2/busybox/manifests/v1
HTTP/1.1 200 OK
Content-Length: 527
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Docker-Content-Digest: sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc"
X-Content-Type-Options: nosniff
Date: Thu, 12 Nov 2020 07:29:46 GMT
删除私有库镜像
进入/etc/docker/registry/config.yml添加,在stroage后面加
delete
enabled: true
修改完后重新启动容器
[root@docker ~]# curl -I -XDELETE http://10.11.67.110:3000/v2/busybox/manifests/sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc
HTTP/1.1 202 Accepted
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Date: Thu, 12 Nov 2020 07:30:22 GMT
Content-Length: 0
查看镜像信息可以看到镜像的标签显示为空 null
[root@docker ~]# curl -XGET http://10.11.67.110:3000/v2/busybox/tags/list
{"name":"busybox","tags":null}
```
## 三部署centos7容器应用
systemd 整合:
因为 systemd 要求 CAPSYSADMIN 权限,从而得到了读取到宿主机 cgroup 的能力CentOS7 中已经用 fakesystemd 代替了 systemd 来解决依赖问题。 如果仍然希望使用 systemd可用参考下面的 Dockerfile
```shell
[root@xingdian ~]# vim Dockerfile
FROM daocloud.io/library/centos:7
MAINTAINER "xingdian" xingdian@qq.com
ENV container docker
RUN yum -y swap -- remove fakesystemd -- install systemd systemd-libs
RUN yum -y update; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
```
这个Dockerfile删除fakesystemd 并安装了 systemd
然后再构建基础镜像:
```shell
[root@xingdian ~]# docker build --rm -t local/c7-systemd .
```
为了使用像上面那样包含 systemd 的容器需要创建一个类似下面的Dockerfile
```shell
[root@xingdian ~]# vim Dockerfile
FROM local/c7-systemd
RUN yum -y install httpd; yum clean all; systemctl enable httpd.service
EXPOSE 80
CMD ["/usr/sbin/init"]
```
构建镜像:
```shell
[root@xingdian ~]# docker build --rm -t local/c7-systemd-httpd .
```
运行包含 systemd 的应用容器:
为了运行一个包含 systemd 的容器,需要使用--privileged选项 并且挂载主机的 cgroups 文件夹。 下面是运行包含 systemd 的 httpd 容器的示例命令:
```shell
[root@xingdian ~]# docker run --privileged -ti -v /sys/fs/cgroup:/sys/fs/cgroup:ro -p 80:80 local/c7-systemd-httpd
```
注意:上条命令不能添加/bin/bash添加了会导致服务不可用而且有些服务可能会发现之前提到的权限不够的问题但是如果不加会运行在前台(没有用-d)可以用ctrl+p+q放到后台去
测试可用:
```shell
# elinks --dump http://docker //下面为apache默认页面
Testing 123..
This page is used to test the proper operation of the [1]Apache HTTP
server after it has been installed. If you can read this page it means
that this site is working properly. This server is powered by [2]CentOS.
```
## 四固定容器IP
#### 1.容器网络
docker安装后默认会创建三种网络类型bridge、host和none
显示当前网络:
```
[root@xingdian ~]# docker network list
NETWORK ID NAME DRIVER SCOPE
90b22f633d2f bridge bridge local
e0b365da7fd2 host host local
da7b7a090837 none null local
```
bridge网络桥接
默认情况下启动、创建容器都是用该模式所以每次docker容器重启时会按照顺序获取对应ip地址这就导致容器每次重启ip都发生变化
none无指定网络
启动容器时可以通过network=none,docker容器不会分配局域网ip
host主机网络
docker容器的网络会附属在主机上两者是互通的
#### 2.创建固定ip容器
创建自定义网络类型,并且指定网段:
```shell
[root@xingdian ~]# docker network create --subnet=192.168.0.0/16 staticnet
```
通过docker network ls可以查看到网络类型中多了一个staticnet
使用新的网络类型创建并启动容器:
```shell
[root@xingdian ~]# docker run -it --name userserver --net staticnet --ip 192.168.0.2 centos:6 /bin/bash
```
通过docker inspect可以查看容器ip为192.168.0.2关闭容器并重启发现容器ip并未发生改变
## 五BUG整理
#### 1.基于centos7的docker容器出现的一个bug
centos7下部署的docker容器中启动服务报错如下
```shell
[root@a3c8baf6961e .ssh]# systemctl restart sshd.service
Failed to get D-Bus connection: Operation not permitted
```
这是centos7容器里面出现的一个BUG
即centos7镜像创建的容器里面安装服务后不能用systemctl/service启动服务centos6的容器里没有这个坑可以通过使用其他的方式启动或者换用centos6的镜像来避免这个错误
解决方案如下:
原因是dbus-daemon没能启动。其实systemctl并不是不可以使用可以将你的CMD设置为/usr/sbin/init即可
这样就会自动将dbus等服务启动起来。即采用 /usr/sbin/init自动启动dbus daemon即把之前的容器关闭并删除docker stop container-id然后重新启动容器注意启动时一定要加上参数--privileged和/usr/sbin/init如下
```shell
[root@localhost ~]# docker run --privileged -it centos7:7.3.1611 /sbin/init
```
#### 2.解决容器内字符集乱码问题
容器内操作:
1. 查找 .vimrc文件
通常有2个地方保存这个文件的
在/etc/文件夹下面是所有用户的vim配置
每个用户的开始登录的文件夹下面
2. 修改.vimrc文件
建议修改当前使用的用户下面,这样只会影响到当前用户
然后添加下面几行,保存后,重新登录即可
```shell
set fileencodings=utf-8,gb2312,gb18030,gbk,ucs-bom,cp936,latin1
set enc=utf8
set fencs=utf8,gbk,gb2312,gb18030
```
修改后如下:
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/TvBv8HgxAve_fExhZw5D_A.png)

668
Docker-MD/Docker进阶.md Normal file
View File

@ -0,0 +1,668 @@
<h1><center>Docker进阶</center></h1>
**作者:行癫(盗版必究)**
------
## 一:深入理解容器
#### 1.Docker的镜像和容器的区别
###### Docker镜像
假设Linux内核是第0层那么无论怎么运行Docker它都是运行于内核层之上的。这个Docker镜像是一个只读的镜像位于第1层它不能被修改或不能保存状态
一个Docker镜像可以构建于另一个Docker镜像之上这种层叠关系可以是多层的。第1层的镜像层我们称之为基础镜像Base Image其他层的镜像除了最顶层我们称之为父层镜像Parent Image。这些镜像继承了他们的父层镜像的所有属性和设置并在Dockerfile中添加了自己的配置
要列出本地所有有效的镜像,可以使用命令
```shell
[root@xingdian ~]# docker images
```
###### Docker容器
Docker容器可以使用命令创建
```shell
[root@xingdian ~]# docker run imagename
```
它会在所有的镜像层之上增加一个可写层。这个可写层有运行在CPU上的进程而且有两个不同的状态
运行态Running和退出态 Exited
这就是Docker容器。当我们使用docker run启动容器Docker容器就进入运行态当我们停止Docker容器时它就进入退出态。当我们有一个正在运行的Docker容器时从运行态到停止态我们对它所做的一切变更都会永久地写到容器的文件系统中。要切记对容器的变更是写入到容器的文件系统的而不是写入到Docker镜像中的。我们可以用同一个镜像启动多个Docker容器这些容器启动后都是活动的彼此还是相互隔离的。我们对其中一个容器所做的变更只会局限于那个容器本身。如果对容器的底层镜像进行修改那么当前正在运行的容器是不受影响的不会发生自动更新现象
64字符的十六进制的字符串来定义容器ID它是容器的唯一标识符。容器之间的交互是依靠容器ID识别的由于容器ID的字符太长我们通常只需键入容器ID的前4个字符即可。当然我们还可以使用容器名
#### 2.容器名称
--name= Assign a name to the container
--为容器分配一个名字,如果没有指定,会自动分配一个随机名称
--docker run子命令的参数
###### 容器命名方式
使用UUID长命名"f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778"
使用UUID短命令"f78375b1c487"
使用Name("xingdian")
注意:
这个UUID标识是由Docker deamon生成的
如果你在执行docker run时没有指定--name那么deamon会自动生成一个随机字符串UUID
但是对于一个容器来说有个name会非常方便当你需要连接其它容器时或者类似需要区分其它容器时使用容器名称可以简化操作。无论容器运行在前台或者后台这个名字都是有效的
如果在使用Docker时有自动化的需求你可以将containerID输出到指定的文件中PIDfile类似于某些应用程序将自身ID输出到文件中方便后续脚本操作
```shell
--cidfile="": Write the container ID to the file
```
#### 3.镜像名称
镜像是Docker最核心的技术之一也是应用发布的标准格式。无论你是用docker pull image或者是在Dockerfile里面写FROM image下载镜像应该是Docker操作里面最频繁的动作之一
下面是在本地机器运行docker images的输出结果
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/Cs3aazbF6VPifePJoVHd0g.png)
常说的"ubuntu"镜像其实不是一个镜像名称而是代表了一个名为ubuntu的Repository同时在这个Repository下面有一系列打了tag的ImageImage的标记是一个GUID为了方便也可以通过Repository:tag来引用
那么Registry又是什么呢Registry存储镜像数据并且提供拉取和上传镜像的功能
Registry中镜像是通过Repository来组织的而每个Repository又包含了若干个Image
Registry包含一个或多个Repository
Repository包含一个或多个Image
Image用GUID表示有一个或多个Tag与之关联
注意:
当一个镜像的名称不足以分辨这个镜像所代表的含义时你可以通过tag将版本信息添加到run命令中以执行特定版本的镜像
```shell
[root@xingdian ~]# docker run ubuntu:14.04
```
#### 4.名字空间
namespace 空间隔离
cgroup 资源限制
rootfs 文件系统
名字空间是 Linux 内核一个强大的特性。每个容器都有自己单独的名字空间,运行在其中的应用都像是在独立的操作系统中运行一样。名字空间保证了容器之间彼此互不影响
###### pid 名字空间
不同用户的进程就是通过 pid 名字空间隔离开的,且不同名字空间中可以有相同 pid
###### net 名字空间
有了pid名字空间, 每个名字空间中的 pid 能够相互隔离,但是网络端口还是共享 host 的端口。网络隔离是通过 net 名字空间实现的,每个 net 名字空间有独立的 网络设备, IP 地址, 路由表, /proc/net 目录。这样每个容器的网络就能隔离开来
###### mnt名字空间
类似 chroot将一个进程放到一个特定的目录执行。mnt 名字空间允许不同名字空间的进程看到的文件结构不同,这样每个名字空间 中的进程所看到的文件目录就被隔离开了
###### uts 名字空间
UTS("UNIX Time-sharing System") 名字空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非主机上的一个进程
###### user 名字空间
每个容器可以有不同的用户和组 id, 也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户
## 二Docker资源限制
虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。这就意味着,虽然第 100 号进程表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU、内存却是可以随时被宿主机上的其他进程或者其他容器占用的。当然这个 100 号进程自己也可能把所有资源吃光
Linux Cgroups 就是 Linux 内核中用来为进程设置资源限制的一个重要功能
Linux Cgroups 的全称是 Linux Control Group。
它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等
Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作
#### 1.系统压力测试工具
stress是一个linux下的压力测试工具专门为那些想要测试自己的系统完全高负荷和监督这些设备运行的用户
安装:
```shell
[root@xingdian ~]# yum install stress -y
```
测试场景举例:
```shell
测试CPU负荷
[root@xingdian ~]# stress c 4
增加4个cpu进程处理sqrt()函数函数以提高系统CPU负荷
内存测试
[root@xingdian ~]# stress i 4 --vm 10 --vm-bytes 1G --vm-hang 100 --timeout 100s
新增4个io进程10个内存分配进程每次分配大小1G分配后不释放测试100S
磁盘I/O测试
# stress d 1 --hdd-bytes 3G
新增1个写进程每次写3G文件块
硬盘测试(不删除)
# stress -i 1 -d 10 --hdd-bytes 3G hdd-noclean
新增1个IO进程10个写进程每次写入3G文件块且不清除会逐步将硬盘耗尽。
```
stress各主用参数说明
```shell
--help 显示帮助信息
--version 显示软件版本信息
-t secs:
--timeout secs指定运行多少秒
-c forks:
--cpu forks 产生多个处理sqrt()函数的CPU进程
-m forks
--vm forks:产生多个处理malloc()内存分配函数的进程,后接进程数量
-i forks
--io forks:产生多个处理sync()函数的磁盘I/O进程
--vm-bytes bytes指定内存的byte数默认值是1
--vm-hang:表示malloc分配的内存多少时间后在free()释放掉
-d :
--hdd:写进程写入固定大小通过mkstemp()函数写入当前目录
--hdd-bytes bytes:指定写的byte数默认1G
--hdd-noclean:不要将写入随机ascii数据的文件unlink则写入的文件不删除会保留在硬盘空间。
```
#### 2.限制CPU share
CPU 资源:
主机上的进程会通过时间分片机制使用 CPUCPU 的量化单位是频率,也就是每秒钟能执行的运算次数。为容器限制 CPU 资源并不能改变 CPU 的运行频率,而是改变每个容器能使用的 CPU 时间片。理想状态下CPU 应该一直处于运算状态(并且进程需要的计算量不会超过 CPU 的处理能力)
Docker 限制 CPU Share
docker 允许用户为每个容器设置一个数字,代表容器的 CPU share默认情况下每个容器的 share 是 1024。这个 share 是相对的,本身并不能代表任何确定的意义。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。docker 会根据主机上运行的容器和进程动态调整每个容器使用 CPU 的时间比例
例子:
如果主机上有两个一直使用 CPU 的容器(为了简化理解,不考虑主机上其他进程),其 CPU share 都是 1024那么两个容器 CPU 使用率都是 50%;如果把其中一个容器的 share 设置为 512那么两者 CPU 的使用率分别为 67% 和 33%;如果删除 share 为 1024 的容器,剩下来容器的 CPU 使用率将会是 100%
好处:
能保证 CPU 尽可能处于运行状态,充分利用 CPU 资源,而且保证所有容器的相对公平
缺点:
无法指定容器使用 CPU 的确定值
设置 CPU share 的参数:
-c --cpu-shares它的值是一个整数
我的机器是 4 核 CPU因此运行一个stress容器,使用 stress 启动 4 个进程来产生计算压力:
```shell
# docker pull progrium/stress
# yum install htop -y
# docker run --rm -it progrium/stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [7] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [8] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [9] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [10] forked
```
在另外一个 terminal 使用 htop 查看资源的使用情况:
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/7n18fo_pTFNkVJh7YB_Y0g.png)
上图中看到CPU 四个核资源都达到了 100%。四个 stress 进程 CPU 使用率没有达到 100% 是因为系统中还有其他机器在运行
为了比较,另外启动一个 share 为 512 的容器
```shell
# docker run --rm -it -c 512 progrium/stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [6] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked
```
因为默认情况下,容器的 CPU share 为 1024所以这两个容器的 CPU 使用率应该大致为 21下面是启动第二个容器之后的监控截图
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/Ja6pTPxoymBhtTRmjhbrRQ.png)
两个容器分别启动了四个 stress 进程,第一个容器 stress 进程 CPU 使用率都在 54% 左右,第二个容器 stress 进程 CPU 使用率在 25% 左右,比例关系大致为 21符合之前的预期
#### 3.限制容器能使用的 CPU 核数
-c --cpu-shares 参数只能限制容器使用 CPU 的比例,或者说优先级,无法确定地限制容器使用 CPU 的具体核数;从 1.13 版本之后docker 提供了 --cpus 参数可以限定容器能使用的 CPU 核数。这个功能可以让我们更精确地设置容器 CPU 使用量,是一种更容易理解也因此更常用的手段
--cpus 后面跟着一个浮点数,代表容器最多使用的核数,可以精确到小数点二位,也就是说容器最小可以使用 0.01 核 CPU
限制容器只能使用 1.5 核数 CPU
```shell
# docker run --rm -it --cpus 1.5 progrium/stress --cpu 3
stress: info: [1] dispatching hogs: 3 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked
```
在容器里启动三个 stress 来跑 CPU 压力,如果不加限制,这个容器会导致 CPU 的使用率为 300% 左右(也就是说会占用三个核的计算能力)。实际的监控如下图:
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/9UmN9NvD1JFTwHY4DGL1mA.png)
可以看到,每个 stress 进程 CPU 使用率大约在 50%,总共的使用率为 150%,符合 1.5 核的设置
如果设置的 --cpus 值大于主机的 CPU 核数docker 会直接报错:
```shell
# docker run --rm -it --cpus 8 progrium/stress --cpu 3
docker: Error response from daemon: Range of CPUs is from 0.01 to 4.00, as there are only 4 CPUs available.
See 'docker run --help'.
```
如果多个容器都设置了 --cpus ,并且它们之和超过主机的 CPU 核数,并不会导致容器失败或者退出,这些容器之间会竞争使用 CPU具体分配的 CPU 数量取决于主机运行情况和容器的 CPU share 值。也就是说 --cpus 只能保证在 CPU 资源充足的情况下容器最多能使用的 CPU 数docker 并不能保证在任何情况下容器都能使用这么多的 CPU因为这根本是不可能的
#### 4.内存资源
默认情况下docker 并没有对容器内存进行限制也就是说容器可以使用主机提供的所有内存。这当然是非常危险的事情如果某个容器运行了恶意的内存消耗软件或者代码有内存泄露很可能会导致主机内存耗尽因此导致服务不可用。对于这种情况docker 会设置 docker daemon 的 OOMout of memory 值,使其在内存不足的时候被杀死的优先级降低。另外,就是你可以为每个容器设置内存使用的上限,一旦超过这个上限,容器会被杀死,而不是耗尽主机的内存
限制内存上限虽然能保护主机,但是也可能会伤害到容器里的服务。如果为服务设置的内存上限太小,会导致服务还在正常工作的时候就被 OOM 杀死;如果设置的过大,会因为调度器算法浪费内存。因此,合理的做法包括
为应用做内存压力测试,理解正常业务需求下使用的内存情况,然后才能进入生产环境使用,一定要限制容器的内存使用上限,尽量保证主机的资源充足,一旦通过监控发现资源不足,就进行扩容或者对容器进行迁移,如果可以(内存资源充足的情况),尽量不要使用 swapswap 的使用会导致内存计算复杂,对调度器非常不友好
###### Docker 限制容器内存使用量:
在 docker 启动参数中,和内存限制有关的包括(参数的值一般是内存大小,也就是一个正数,后面跟着内存单位 b、k、m、g分别对应 bytes、KB、MB、和 GB
```shell
-m --memory容器能使用的最大内存大小最小值为 4m
--memory-swap容器能够使用的 swap 大小
--memory-swappiness默认情况下主机可以把容器使用的匿名页anonymous pageswap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例
--memory-reservation设置一个内存使用的 soft limit(软限制),如果 docker 发现主机内存不足,会执行 OOM 操作。这个值必须小于 --memory 设置的值
--kernel-memory容器能够使用的 kernel memory (内核内存)大小,最小值为 4m。
--oom-kill-disable是否运行 OOM 的时候杀死容器。只有设置了 -m才可以把这个选项设置为 false(假),否则容器会耗尽主机内存,而且导致主机应用被杀死
```
关于 --memory-swap 的设置: --memory-swap 必须在 --memory 也配置的情况下才能有用
如果 --memory-swap 的值大于 --memory那么容器能使用的总内存内存 + swap为 --memory-swap 的值,能使用的 swap 值为 --memory-swap 减去 --memory 的值
如果 --memory-swap 为 0或者和 --memory 的值相同,那么容器能使用两倍于内存的 swap 大小,如果 --memory 对应的值是 200M那么容器可以使用 400M swap
如果 --memory-swap 的值为 -1那么不限制 swap 的使用,也就是说主机有多少 swap容器都可以使用
如果限制容器的内存使用为 64M在申请 64M 资源的情况下,容器运行正常(如果主机上内存非常紧张,并不一定能保证这一点)
```shell
# docker run --rm -it -m 64m progrium/stress --vm 1 --vm-bytes 64M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 67108864 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] sleeping forever with allocated memory
.....
```
而如果申请 100M 内存,会发现容器里的进程被 kill 掉了worker 7 got signal 9signal 9 就是 kill 信号)
```shell
# docker run --rm -it -m 64m progrium/stress --vm 1 --vm-bytes 100M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 104857600 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (415) &lt;-- worker 7 got signal 9
stress: WARN: [1] (417) now reaping child worker processes
stress: FAIL: [1] (421) kill error: No such process
stress: FAIL: [1] (451) failed run completed in 0s
```
#### 5.IO资源
对于磁盘来说,考量的参数是容量和读写速度,因此对容器的磁盘限制也应该从这两个维度出发。目前 docker 支持对磁盘的读写速度进行限制,但是并没有方法能限制容器能使用的磁盘容量(一旦磁盘 mount 到容器里,容器就能够使用磁盘的所有容量)
限制磁盘的读写速率docker 允许你直接限制磁盘的读写速率,对应的参数有:
--device-read-bps磁盘每秒最多可以读多少比特bytes
--device-write-bps磁盘每秒最多可以写多少比特bytes
上面两个参数的值都是磁盘以及对应的速率,限制 limit 为正整数,单位可以是 kb、mb 和 gb
比如可以把设备的读速率限制在 1mb
```shell
# docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb ubuntu:16.04 bash
root@6c048edef769:/# cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device
8:0 1048576
root@6c048edef769:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5M count=10
10+0 records in
10+0 records out
52428800 bytes (52 MB) copied, 50.0154 s, 1.0 MB/s
```
从磁盘中读取 50m 花费了 50s 左右,说明磁盘速率限制起了作用
另外两个参数可以限制磁盘读写频率(每秒能执行多少次读写操作):
--device-read-iops磁盘每秒最多可以执行多少 IO 读操作
--device-write-iops磁盘每秒最多可以执行多少 IO 写操作
上面两个参数的值都是磁盘以及对应的 IO 上限
比如,可以让磁盘每秒最多读 100 次:
```shell
# docker run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:100 ubuntu:16.04 bash
root@2e3026e9ccd2:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=1k count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 9.9159 s, 103 kB/s
```
从测试中可以看出,容器设置了读操作的 iops 为 100在容器内部从 block 中读取 1m 数据(每次 1k一共要读 1000 次),共计耗时约 10s换算起来就是 100
总结:
Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合
## 三:理解容器文件系统
Namespace 的作用是"隔离",它让应用进程只能看到该 Namespace 内的"世界";而 Cgroups 的作用是"限制",它给这个"世界"围上了一圈看不见的墙。这么一折腾,进程就真的被"装"在了一个与世隔绝的房间里,而这些房间就是 PaaS 项目赖以生存的应用"沙盒"
可是,还有一个问题:这个房间四周虽然有了墙,但是如果容器进程低头一看地面,又是怎样一副景象呢?
换句话说,容器里的进程看到的文件系统又是什么样子的呢?
可能你立刻就能想到,这一定是一个关于 Mount Namespace 的问题:容器里的应用进程,理应看到一份完全独立的文件系统。这样,它就可以在自己的容器目录(比如 /tmp下进行操作而完全不会受宿主机以及其他容器的影响
那么,真实情况是这样吗?
一段小程序:作用是,在创建子进程时开启指定的 Namespace。使用它来验证一下刚刚提到的问题
```shell
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container - inside the container!\n");
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent - start a container!\n");
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | SIGCHLD , NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
```
代码功能非常简单:在 main 函数里,通过 clone() 系统调用创建了一个新的子进程 container_main并且声明要为它启用 Mount NamespaceCLONE_NEWNS 标志)
而这个子进程执行的,是一个"/bin/bash"程序,也就是一个 shell。所以这个 shell 就运行在了 Mount Namespace 的隔离环境中
编译一下这个程序:
```shell
[root@xingdian ~]# gcc -o ns ns.c
[root@xingdian ~]# ./ns
Parent - start a container!
Container - inside the container!
```
这样,就进入了这个"容器"当中(表面上看不大出来-xingdian注。可是如果在"容器"里执行一下 ls 指令的话,就会发现一个有趣的现象: /tmp 目录下的内容跟宿主机的内容是一样的
```shell
[root@xingdian ~]# ls /tmp
# 你会看到好多宿主机的文件
```
也就是说:即使开启了 Mount Namespace容器进程看到的文件系统也跟宿主机完全一样
这是怎么回事呢?
仔细思考一下你会发现这其实并不难理解Mount Namespace 修改的,是容器进程对文件系统"挂载点"的认知。但是,这也就意味着,只有在"挂载"这个操作发生之后,进程的视图才会被改变。而在此之前,新创建的容器会直接继承宿主机的各个挂载点
这时,你可能已经想到了一个解决办法:创建新进程时,除了声明要启用 Mount Namespace 之外,还可以告诉容器进程,有哪些目录需要重新挂载,就比如这个 /tmp 目录。于是,在容器进程执行前可以添加一步重新挂载 /tmp 目录的操作:
```shell
int container_main(void* arg)
{
printf("Container - inside the container!\n");
// 如果你的机器的根目录的挂载类型是 shared那必须先重新挂载根目录
// mount("", "/", NULL, MS_PRIVATE, "");
mount("none", "/tmp", "tmpfs", 0, "");
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
```
在修改后的代码里,在容器进程启动之前,加上了一句 mount("none", "/tmp", "tmpfs", 0, "") 语句。就这样,告诉了容器以 tmpfs内存盘格式重int container_main(void* arg)
```shell
{
printf("Container - inside the container!\n");
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}新挂载了 /tmp 目录。
```
编译执行修改后的代码结果又如何呢?试验一下:
```shell
[root@xingdian ~]# gcc -o ns ns.c
[root@xingdian ~]# ./ns
Parent - start a container!
Container - inside the container!
[root@xingdian ~]# ls /tmp
这次 /tmp 变成了一个空目录,这意味着重新挂载生效了。
用 mount -l 检查:
[root@xingdian ~]# mount -l | grep tmpfs
none on /tmp type tmpfs (rw,relatime)
```
容器里的 /tmp 目录是以 tmpfs 方式单独挂载的。可以卸载一下/tmp目录看看效果
更重要的是,因为创建的新进程启用了 Mount Namespace所以这次重新挂载的操作只在容器进程的 Mount Namespace 中有效。如果在宿主机上用 mount -l 来检查一下这个挂载,你会发现它是不存在的:
在宿主机上
```shell
[root@xingdian ~]# mount -l | grep tmpfs
```
![img](https://xingdian-image.oss-cn-beijing.aliyuncs.com/xingdian-image/GDC-shjNkp1Xrs3PUgEjRw.png)
这就是 Mount Namespace 跟其他 Namespace 的使用略有不同的地方它对容器进程视图的改变一定是伴随着挂载操作mount才能生效
我们希望的是:每当创建一个新容器时,我希望容器进程看到的文件系统就是一个独立的隔离环境,而不是继承自宿主机的文件系统。怎么才能做到这一点呢?
可以在容器进程启动之前重新挂载它的整个根目录"/"。而由于 Mount Namespace 的存在,这个挂载对宿主机不可见,所以容器进程就可以在里面随便折腾了
在 Linux 操作系统里,有一个名为 chroot 的命令可以帮助你在 shell 中方便地完成这个工作。顾名思义,它的作用就是帮你"change root file system",即改变进程的根目录到你指定的位置
假设,现在有一个 /home/xingdian/test 目录,想要把它作为一个 /bin/bash 进程的根目录
首先,创建一个 test 目录和几个 lib 文件夹:
```shell
[root@xingdian ~]# mkdir -p /home/xingdian/test
[root@xingdian ~]# mkdir -p /home/xingdian/test/{bin,lib64,lib}
然后,把 bash 命令拷贝到 test 目录对应的 bin 路径下:
# cp -v /bin/{bash,ls} /home/xingdian/test/bin
```
接下来,把 ls和bash命令需要的所有 so 文件,也拷贝到 test 目录对应的 lib 路径下。找到 so 文件可以用 ldd 命令ldd 列出动态依赖库
```shell
[root@xingdian ~]# list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"
[root@xingdian ~]# for i in $list; do cp -v "$i" "/home/xingdian/test/${i}"; done
[root@xingdian ~]# list="$(ldd /bin/bash | egrep -o '/lib.*\.[0-9]')"
[root@xingdian ~]# for i in $list; do cp -v "$i" "/home/xingdian/test/${i}"; done
```
最后,执行 chroot 命令,告诉操作系统,将使用 /home/xingdian/test 目录作为 /bin/bash 进程的根目录:
```shell
[root@xingdian ~]# chroot /home/xingdian/test /bin/bash
```
这时,执行 "ls /",就会看到,它返回的都是 /home/xingdian/test 目录下面的内容,而不是宿主机的内容
更重要的是,对于被 chroot 的进程来说,它并不会感受到自己的根目录已经被"修改"成 /home/xingdian/test 了
这种视图被修改的原理,是不是跟我之前介绍的 Linux Namespace 很类似呢
实际上Mount Namespace 正是基于对 chroot 的不断改良才被发明出来的,它也是 Linux 操作系统里的第一个 Namespace
为了能够让容器的这个根目录看起来更"真实",一般会在这个容器的根目录下挂载一个完整操作系统的文件系统,比如 Ubuntu16.04 的 ISO。这样在容器启动之后在容器里通过执行 "ls /" 查看根目录下的内容,就是 Ubuntu 16.04 的所有目录和文件
而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的"容器镜像"。它还有一个更为专业的名字叫作rootfs根文件系统
所以,一个最常见的 rootfs或者说容器镜像会包括如下所示的一些目录和文件比如 /bin/etc/proc 等等
```shell
[root@xingdian ~]# ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var
```
而进入容器之后执行的 /bin/bash就是 /bin 目录下的可执行文件,与宿主机的 /bin/bash 完全不同
所以,对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:
启用 Linux Namespace 配置
设置指定的 Cgroups 参数
切换进程的根目录Change Root
这样一个完整的容器就诞生了。不过Docker 项目在最后一步的切换上会优先使用 pivot_root 系统调用,如果系统不支持,才会使用 chroot。这两个系统调用功能类似
rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核,同一台机器上的所有容器,都共享宿主机操作系统的内核。如果你的应用程序需要配置内核参数、加载额外的内核模块,以及跟内核进行直接的交互,你就需要注意了:这些操作和依赖的对象,都是宿主机操作系统的内核,它对于该机器上的所有容器来说是一个"全局变量",牵一发而动全身
在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像
这是容器相比于虚拟机的缺陷之一:虚拟机不仅有模拟出来的硬件机器充当沙盒,而且每个沙盒里还运行着一个完整的 Guest OS 给应用随便用
容器的一致性
由于云端与本地服务器环境不同,应用的打包过程,一直是使用 PaaS 时最"痛苦"的一个步骤
但有了容器镜像(即 rootfs之后这个问题被解决了
由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起
对于大多数开发者而言,他们对应用依赖的理解,一直局限在编程语言层面。比如 Golang 的 Godeps.json。但实际上一个一直以来很容易被忽视的事实是对一个应用来说操作系统本身才是它运行所需要的最完整的"依赖库"
有了容器镜像"打包操作系统"的能力,这个最基础的依赖环境也终于变成了应用沙盒的一部分。这就赋予了容器所谓的一致性:无论在本地、云端,还是在一台任何地方的机器上,用户只需要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了
这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟
联合文件系统Union File System 也叫 UnionFS
Docker 公司在实现 Docker 镜像时并没有沿用以前制作 rootfs 的标准流程,做了一个创新:
Docker 在镜像的设计中引入了层layer的概念。也就是说用户制作镜像的每一步操作都会生成一个层也就是一个增量 rootfs。用到了一种叫作联合文件系统Union File System的能力
主要的功能是将多个不同位置的目录联合挂载union mount到同一个目录下
比如,现在有两个目录 A 和 B它们分别有两个文件
```shell
[root@xingdian ~]# tree
├── A
│ ├── a
│ └── x
└── B
├── b
└── x
```
然后,使用联合挂载的方式,将这两个目录挂载到一个公共的目录 C 上:
```shell
[root@xingdian ~]# mkdir C
[root@xingdian ~]# yum install funionfs -y //我这里用的是centos7自带的联合文件系统效果一样
[root@xingdian ~]# funionfs -o dirs=./A:./B none ./C
```
再查看目录 C 的内容,就能看到目录 A 和 B 下的文件被合并到了一起:
```shell
[root@xingdian ~]# tree ./C
./C
├── a
├── b
└── x
```
可以看到,在这个合并后的目录 C 里,有 a、b、x 三个文件,并且 x 文件只有一份。这,就是"合并"的含义
此外,如果在目录 C 里对 a、b、x 文件做修改,这些修改也会在对应的目录 A、B 中生效
```shell
[root@xingdian ~]# echo hello >> C/a
[root@xingdian ~]# cat C/a
hello
[root@xingdian ~]# cat A/a
hello
[root@xingdian ~]# echo hello1 >> A/a
[root@xingdian ~]# cat A/a
hello
hello1
[root@xingdian ~]# cat C/a
hello
hello1
```

View File

@ -0,0 +1,326 @@
<h1><center>Docker镜像构建</center></h1>
**作者:行癫(盗版必究)**
------
## 一:创建自己的镜像
#### 1.将容器的文件系统打包成tar包
将容器的文件系统打包成tar文件,也就是把正在运行的容器直接导出为tar包的镜像文件
导出:
```shell
export
Export a container's filesystem as a tar archive
有两种方式elated_lovelace为容器名
第一种:
[root@xingdian ~]# docker export -o elated_lovelace.tar elated_lovelace
第二种:
[root@xingdian ~]# docker export 容器名称 > 镜像.tar
```
导入:
```shell
导入镜像归档文件到其他宿主机:
import
Import the contents from a tarball to create a filesystem image
[root@xingdian ~]# docker import elated_lovelace.tar elated_lovelace:v1
```
注意:
如果导入镜像时没有起名字,随后可以单独起名字(没有名字和tag)可以手动加tag
```shell
[root@xingdian ~]# docker tag 镜像ID mycentos:7
```
#### 2.镜像迁移
保存一台宿主机上的镜像为tar文件然后可以导入到其他的宿主机上
```shell
save
Save an image(s) to a tar archive
将镜像打包与下面的load命令相对应
[root@xingdian ~]# docker save -o nginx.tar nginx
load
Load an image from a tar archive or STDIN
与上面的save命令相对应将上面sava命令打包的镜像通过load命令导入
[root@xingdian ~]# docker load < nginx.tar
```
注:
tar文件的名称和保存的镜像名称没有关系
导入的镜像如果没有名称自己打tag起名字
扩展export和save的区别
export相当于容器快照容器快照文件将丢弃所有的历史记录和元数据信息
save没有这个现象就是完整的
#### 3.通过容器创建本地镜像
背景:
容器运行起来后,又在里面做了一些操作,并且要把操作结果保存到镜像里
方案:
使用 docker commit 指令把一个正在运行的容器直接提交为一个镜像。commit 是提交的意思,类似我要生成一个新的版本
例子:
在容器内部新建了一个文件
```shell
[root@xingdian ~]# docker exec -it 4ddf4638572d /bin/sh
root@4ddf4638572d:/app# touch test.txt
root@4ddf4638572d:/app# exit
# 将这个新建的文件提交到镜像中保存
[root@xingdian ~]# docker commit 4ddf4638572d xingdian/helloworld:v2
```
例子:
```shell
[root@xingdian ~]# docker commit -m "my images version1" -a "xingdian" 108a85b1ed99 daocloud.io/ubuntu:v2
sha256:ffa8a185ee526a9b0d8772740231448a25855031f25c61c1b63077220469b057
-m 添加注释
-a 作者
108a85b1ed99 容器环境id
daocloud.io/ubuntu:v2 镜像名称hub的名称/镜像名称tag
-ppause=true 提交时暂停容器运行
```
## 二利用Dockerfile创建镜像
通过Dockerfile创建镜像虽然可以自己制作 rootfs(根文件系统)但Docker 提供了一种更便捷的方式,叫作 Dockerfile
docker build命令用于根据给定的Dockerfile和上下文以构建Docker镜像
docker build语法
```shell
[root@xingdian ~]# docker build [OPTIONS] <PATH | URL | ->
```
#### 1. 常用选项说明
```shell
--build-arg设置构建时的变量
--no-cache默认false。设置该选项将不使用Build Cache构建镜像
--pull默认false。设置该选项总是尝试pull镜像的最新版本
--compress默认false。设置该选项将使用gzip压缩构建的上下文
--disable-content-trust默认true。设置该选项将对镜像进行验证
--file, -fDockerfile的完整路径默认值为PATH/Dockerfile
--isolation默认--isolation="default"即Linux命名空间其他还有process或hyperv
--label为生成的镜像设置metadata
--squash默认false。设置该选项将新构建出的多个层压缩为一个新层但是将无法在多个镜像之间共享新层设置该选项实际上是创建了新image同时保留原有image。
--tag, -t镜像的名字及tag通常name:tag或者name格式可以在一次构建中为一个镜像设置多个tag
--network默认default。设置该选项Set the networking mode for the RUN instructions during build
--quiet, -q 默认false。设置该选项Suppress the build output and print image ID on success
--force-rm默认false。设置该选项总是删除掉中间环节的容器
--rm默认--rm=true即整个构建过程成功后删除中间环节的容器
```
#### 2. PATH | URL | -说明
给出命令执行的上下文
上下文可以是构建执行所在的本地路径也可以是远程URL如Git库、tarball或文本文件等。如果是Git库如https://github.com/docker/rootfs.git#container:docker则隐含先执行git clone --depth 1 --recursive到本地临时目录然后再将该临时目录发送给构建进程
构建镜像的进程中可以通过ADD命令将上下文中的任何文件注意文件必须在上下文中加入到镜像中
-表示通过STDIN给出Dockerfile或上下文
示例:
```shell
[root@xingdian ~]# docker build - < Dockerfile
```
说明该构建过程只有Dockerfile没有上下文
```shell
[root@xingdian ~]# docker build - < context.tar.gz
```
说明其中Dockerfile位于context.tar.gz的根路径
```shell
[root@xingdian ~]# docker build -t champagne/bbauto:latest -t champagne/bbauto:v2.1 .
[root@xingdian ~]# docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
```
#### 3. 创建镜像所在的文件夹和Dockerfile文件
```shell
mkdir sinatra
cd sinatra
touch Dockerfile
```
在Dockerfile文件中写入指令每一条指令都会更新镜像的信息
```shell
# This is a comment
FROM daocloud.io/library/ubuntu
MAINTAINER xingdian xingdian@localhost.localdomain
RUN apt-get update && apt-get install -y ruby ruby-dev
```
格式说明:
每行命令都是以 INSTRUCTION statement 形式,就是命令+ 清单的模式。命令要大写,"#"是注解
FROM 命令是告诉docker 我们的镜像什么
MAINTAINER 是描述 镜像的创建人
RUN 命令是在镜像内部执行。就是说他后面的命令应该是针对镜像可以运行的命令
#### 4.创建镜像
命令:
```shell
docker build -t xingdian/sinatra:v2 .
```
docker build 是docker创建镜像的命令
-t 是标识新建的镜像
sinatra是仓库的名称
:v2 是tag
"."是用来指明 我们的使用的Dockerfile文件当前目录
详细执行过程:
```shell
[root@master sinatra]# docker build -t xingdian/sinatra:v2 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM daocloud.io/ubuntu:14.04
Trying to pull repository daocloud.io/ubuntu ...
14.04: Pulling from daocloud.io/ubuntu
f3ead5e8856b: Pull complete
Digest: sha256:ea2b82924b078d9c8b5d3f0db585297a5cd5b9c2f7b60258cdbf9d3b9181d828
---> 2ff3b426bbaa
Step 2 : MAINTAINER xingdian xingdian@localhost.localdomain
---> Running in 948396c9edaa
---> 227da301bad8
Removing intermediate container 948396c9edaa
Step 3 : RUN apt-get update && apt-get install -y ruby ruby-dev
...
Step 4 : RUN gem install sinatra
---> Running in 89234cb493d9
```
#### 5.创建完成后,从镜像创建容器
```shell
[root@xingdian ~]# docker run -t -i xingdian/sinatra:v2 /bin/bash
```
## 三企业级Dockerfile文件构建容器
#### 1.Dockerfile文件构建nginx
```shell
[root@xingdian ~]# cat Dockerfile
FROM centos:7.2.1511
ENV TZ=Asia/Shanghai
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
RUN yum -y install gcc openssl openssl-devel pcre-devel zlib-devel make
ADD nginx-1.14.0.tar.gz /opt/
WORKDIR /opt/nginx-1.14.0
RUN ./configure --prefix=/opt/nginx
RUN make && make install
WORKDIR /opt/nginx
RUN rm -rf /opt/nginx-1.14.0
ENV NGINX_HOME=/opt/nginx
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/nginx/sbin
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
注意:
Nginx的docker仓库原文说明如下
If you add a custom CMD in the Dockerfile, be sure to include -g daemon off; in the CMD in order fornginx to stay in the foreground, so that Docker can track the process properly (otherwise your container will stop immediately after starting)!
Running nginx in debug mode
Images since version 1.9.8 come with nginx-debug binary that produces verbose output when using higher log levels. It can be used with simple CMD substitution:
$ docker run --name my-nginx -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx nginx -g 'daemon off;'
Similar configuration in docker-compose.yml may look like this:
web:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
command: [nginx, '-g', 'daemon off;']
```
#### 2.Dockerfile文件构建redis
```shell
FROM centos:7.2.1511
MAINTAINER qf
ENV TZ=Asia/Shanghai
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
RUN yum -y install gcc make
ADD redis-4.0.9.tar.gz /opt/
RUN cd /opt/ && mv redis-4.0.9 redis && cd /opt/redis && make && make install
RUN mkdir -p /opt/redis/logs && mkdir -p /opt/redis/data && mkdir -p /opt/redis/conf && cp /opt/redis/redis.conf /opt/redis/conf/ && cp /opt/redis/src/redis-trib.rb /usr/local/bin/
EXPOSE 6379
CMD ["redis-server","/opt/redis/conf/redis.conf"]
基于哨兵模式的redis镜像
FROM centos:7.2.1511
MAINTAINER redis4 jichujingxiang
ENV TZ=Asia/Shanghai
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
RUN yum -y install gcc make
ADD redis-4.0.9.tar.gz /opt/
ADD run.sh /
RUN cd /opt/ && mv redis-4.0.9 redis && cd /opt/redis && make && make install
RUN mkdir -p /opt/redis/logs && mkdir -p /opt/redis/data && mkdir -p /opt/redis/conf && cp /opt/redis/redis.conf /opt/redis/conf/ && cp /opt/redis/src/redis-trib.rb /usr/local/bin/ && cp /opt/redis/sentinel.conf /opt/redis/conf/ && chmod 777 /run.sh
EXPOSE 6379
CMD ["./run.sh"]
#cat /run.sh
#!/usr/bin/bash
#2018/10/24
#行癫
cd /opt/redis/src/
./redis-server /opt/redis/conf/redis.conf & #这一个要放在后台运行,不然下面的启动不了
./redis-sentinel /opt/redis/conf/sentinel.conf
```
#### 3.Dockerfile文件构建jenkins
```shell
行癫(亲测)
FROM local/c7-systemd
ADD jdk-9.0.1_linux-x64_bin.tar.gz /usr/local/
ADD apache-tomcat-9.0.14.tar.gz /usr/local/
WORKDIR /usr/local/
ENV JAVA_HOME=/usr/local/java
ENV PATH=$JAVA_HOME/bin:$PATH
ENV CATALINA_HOME=/usr/local/tomcat
ENV export JAVA_HOME CATALINA_HOME PATH
RUN mv jdk-9.0.1 java && mv apache-tomcat-9.0.14 tomcat
COPY jenkins.war /usr/local/tomcat/webapps/
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
```