云计算之k8s系列_第三回——docker实践
实践操作
用docker部署一个用python编写的web应用
[root@centos7 ~]# vim app.py
from flask import Flask
import socket
import os
app = Flask(__name__)
@app.route('/')
def hello():
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
if __name__=="__main__":
app.run(host='0.0.0.0', port=80)
这个应用的依赖,并定义在了同目录下的requirements.txt文件中
[root@centos7 ~]# cat requirements.txt
Flask
制作Dockerfile
[root@centos7 ~]# vim Dockerfile
FROM docker.io/python
WORKDIR /app
ADD . /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
EXPOSE 80
ENV NAME World
CMD [ "python" , "app.py" ]
使用Dockerfile制作这个镜像
[root@centos7 ~]# docker built -t helloworld .
[root@centos7 ~]# docker images
使用生成的镜像启动容器
[root@centos7 ~]# docker run -d -p 4000:80 helloworld
[root@centos7 ~]# ss -anptu | grep :4000
tcp LISTEN 0 1024 :::4000 :::* users:(("docker-proxy-cu",pid=16548,fd=4))
[root@centos7 ~]# curl 127.0.0.1:4000
<h3>Hello World!</h3><b>Hostname:</b> d7807cac63e5<br/>
注册docker hub账号,使用docker login登录上传镜像
#此处已有账号,不做注册,如果没有dockerhub账号的话,可以到官网注册
[root@centos7 docker]# docker login
Username: daneiyunzhijia
Password:
Login Succeeded
[root@centos7 docker]# docker tag helloworld daneiyunzhijia/helloworld:latest
[root@centos7 docker]# docker push daneiyunzhijia/helloworld:latest
进入刚刚创建的容器,创建文件,退出,并使用docker commit生成新的镜像
[root@centos7 docker]# docker exec -it c2 /bin/sh
# touch test.txt
# exit
[root@centos7 docker]# docker commit c2 daneiyunzhijia/helloworld:v2
思考:docker exec是怎么做到进入容器的?
一个进程的namespace在宿主机上是以一个文件的方式存在的。
[root@centos7 docker]# docker inspect c2 | grep Pid
"Pid": 16722
[root@centos7 docker]# ls -l /proc/16722/ns
total 0
lrwxrwxrwx 1 root root 0 May 26 23:32 ipc -> ipc:[4026532233]
lrwxrwxrwx 1 root root 0 May 26 23:11 mnt -> mnt:[4026532231]
lrwxrwxrwx 1 root root 0 May 26 23:11 net -> net:[4026532293]
lrwxrwxrwx 1 root root 0 May 26 23:32 pid -> pid:[4026532234]
lrwxrwxrwx 1 root root 0 May 26 23:40 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 May 26 23:32 uts -> uts:[4026532232]
可以看到,一个进程的每种namespace,都在它对应的/proc/容器进程id/ns下有一个对应的虚拟文件,并且连接到一个真实的namespace文件上。有了这个功能,就可以加入到一个已经存在的namespace当中。
以上就是docker exec的实现原理:一个进程,可以选择加入到某个进程已有的namespace中,从而达到进入这个进程所在容器的目的。
一旦一个进程加入到另一个进程的namespace中,在宿主机的namespace文件上,也会有所体现。
如果创建容器时指定 --net=host,就意味着这个容器不会为进程启用network namespace。
docker commit
实际上就是在容器运行起来后,把最上层的“可读写层” , 加上原先容器镜像的只读层,打包组成一个新的镜像。当然,下面只读层在宿主机上是共享的,不会占用额外的空间。
copy-on-write: cow
在容器里对镜像rootfs所做的任何修改,都会被操作系统先复制到可读写层,然后在修改,这称为copy-on-write。
volume数据卷
volume机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
方法:冒号前面的部分对应宿主机目录,冒号后面的部分代表容器目录,不写冒号,代表宿主机和容器目录一样
#方法1
docker run -v /test centos ...
#方法2
docker run -v /home:/test ...
执行 -v 挂载操作时,容器进程已经创建了,所以,这个挂载只在容器可见,宿主机不可见,保证了容器的隔离性不会被volume打破。
所以,在一个正确的时机,进行一次绑定挂载,docker就可以成功地将一个宿主机上的目录或文件挂载到容器中。
docker 命令补充:
docker images #查看已有的本地镜像
docker search rhel #搜索docker镜像,本地和远程都会搜索
docker pull 镜像名 #下载镜像
docker push 镜像名 #上传镜像
docker load < busybox.tar #导入镜像
docker save 镜像名 > busybox.tar #保存镜像到本地文件
docker run -itd -v /a:/b -p 8080:80 镜像名 #使用镜像启动容器
docker ps -aq #查看已经运行的和没有运行的容器
docker history #查看镜像制作的历史
docker inspect #查看容器或者镜像的详细信息
docker rmi 镜像名 #删除镜像
docker tag 镜像名 新名:v1 #修改镜像标签
docker run / stop / restart #启动/停止/重启容器
docker attach | exec #进入容器
docker top #查看进程
docker rm #删除容器
docker commit 容器id 镜像名:标签 #使用容器,创建镜像
Dockerfile
FROM #基础镜像
MAINTAINER #维护者
EXPOSE #开放端口
ENV #设置环境变量
ADD #将本地文件加入到容器中
RUN #在容器中执行命令
WORKDIR #定义工作目录
CMD #容器启动时执行的命令
docker build -t imagename . #使用Dockerfile创建镜像
docker私有仓库
docker pull registry
vim /etc/docker/daemon.json
{
"insecure-registries": [ "仓库宿主机ip:5000" ]
}
docker run -d -p 5000:5000 registry
docker tag 镜像 ip:5000/镜像:标签
docker push ip:5000/镜像:标签
docker网络
网络模型:
bridge
host
none
docker network create --driver bridge docker1
docker network list
docker run --network=docker1 -itd nginx
docker 总结:
docker是分层显示地,最下层是docker镜像的只读层。在只读层纸上,是docker自己添加的init层,用来存放被临时修改过的/etc/hosts等文件。而rootfs之上是一个读写层,它以copy-on-write 的方式存放任何对只读层的修改,容器声明的volume的挂载点,也出现在这一层。