Docker 读书笔记(三)

  • 虚拟机与linux容器的区别

linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需捆绑一整套操作系统,只需要软件工作需的库资源和设置。系统因此变得更高效更轻量并保证部署在任何环境中的软件都能始终如一地运行。docker 启动快,秒级的

虚拟机是带环境安装的一种解决方案(连硬件也模拟),它可以在一种操作系统里运行另一种操作系统,比如windows下运行linux系统。而对底层系统来说,虚拟机就是一个普通文件,不需要就删掉。虚拟机启动慢,分钟级的,虚拟机缺点:占用资源多,冗余步骤多,启动慢

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便
每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源,耦合度低。

Docker带来的好处:DevOps开发自运维,一次构建,随处运行。更快速的应用交付和部署;更便捷的升级和扩缩容;更简单的系统运维;更高效的计算资源利用;

Docker 安装前提:

CentOS:CentOS6.5 64-bit 或更高的版本,内核版本:2.6.32-431或更高版本

//查看CentOS系统版本
[root@VM_IP_centos ~]# cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core)

//查看内核
[root@VM_IP_centos ~]# uname -r
3.10.0-957.21.2.el7.x86_64
Docker三要素
  • hello world 实例
docker run hello-world

//先本机去找hello-world镜像,用这个镜像生成一个容器。如果本地没有,从远程仓库拉取,下载到本地,然后以镜像为模板生产容器实例运行

docker run 的过程:

首先系统要有一个docker daemon的后台进程在运行,当刚才这行命令敲下时,发生了如下动作:

  1. docker client(即:docker终端命令行)会调用docker daemon请求启动一个容器,
  2. docker daemon会向host os(即:linux)请求创建容器
  3. linux会创建一个空的容器(可以简单理解为:一个未安装操作系统的裸机,只有虚拟出来的CPU、内存等硬件资源)
  4. docker daemon请检查本机是否存在docker镜像文件,如果有,则加载到容器中
  5. 将镜像文件加载到容器中
  6. 如果在第4步检查本机镜像文件时,发现文件不存在,则会到默认的docker镜像注册机构(即:docker hub网站或是指定的仓库)去联网下载,下载回来后,再进行装载到容器的动作
此图像的alt属性为空;文件名为image-8.png
  • Docker 为什么比虚拟机快

1.docker 有着比虚拟机更少的抽象层。docker 不需要实现Hypervisor硬件资源虚拟化,docker 容器上的程序直接使用都是实际物理机上的硬件资源
2.docker 利用宿主机的内核,而不需要guest os, docker 不需要和虚拟机一样重新加载一个操作系统内核,避免引导,加载操作系统内核比较费时资源的过程。而docker 直接利用 宿主机的操作系统,所以新建docker 比较快

  • 容器虚拟化技术与虚拟机有什么不同?
Docker容器虚拟机VM
操作系统与宿主机共享OS宿主机OS上运行虚拟机OS
存储大小镜像小,便于存储与传输镜像庞大(vmdk,vdi等)
运行性能几乎无额外性能损失操作系统额外的CPU,内存消耗
移植性轻便、灵活、适应于linux笨重,与虚拟化技术耦合度高
硬件亲和性面向软件开发者面向硬件运维者
部署速度快速,秒级较慢,10s以上
  • Docker 命令总结
帮助与镜像命令
容器命令1
容器命令2
容器命令3
容器命令4
  • Docker 命令小结
小结
  • UnionFS(联合文件系统):Union文件系统是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite serveral directories into a single virtual filesystem)。Union文件系统是Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作出具体的应用镜像
  • Docker镜像加载原理:

docker 的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。bootfs 主要包含bootloader和kernel,bootloader主要引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完,整个内核就在内存中了,此时内存使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs,在bootfs之上,包含的就是典型的linux系统中的/dev, /proc, /bin, /etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如ubuntu和centos等。

k8s 中服务是如何沟通的?

文章来源: https://www.jianshu.com/p/9fae09876eb7 (这是我迄今为止看到讲k8s 讲的比较的好的一篇文章),即便你刚刚接触k8s, 看完他,也能减少很多疑惑

挡在Pod前面的Service

第一个疑问:有了Pod,让它们互相访问就可以了,为什么还需要type=Service类型的Service对象呢?答案是:不可以,因为Pod是有生命周期的,他可能随时被创建也可能随时销毁,而每次新建Pod, 都是随机分配IP的。并且k8s会自动调控Pod数量,这就导致Pod之间直接访问不太现实,如果有一个入口,可以动态绑定那些相同服务的Pod,并将其开放在固定端口上,这样访问起来就方便多了,这个入口在k8s中被称为service, 简称为svc。

挡在Pod前的Service

svc不会自己提供服务,他身后一定要有实质的应用来提供服务(不一定是Pod), 我们用rs来创建Pod也是可以的

先创建一个kubia-replicaset.yaml,并填入如下内容:

apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080

这个文件将创建3个Pod,每个Pod都包含一个app: kubia的标签,并开放提供服务的8080端口。执行如下命令进行创建:

kubectl create -f kubia-replicaset.yaml

然后kubectl get pods 可以看到创建出的三个pod,接下来我们创建一个svc来提供对这三个pod来访问。新建kubia-svc.yaml文件,并填入如下内容:

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http # 请为所有端口指定名称
    port: 80 # 对外开放的服务端口
    targetPort: 8080 # 后方 pod 的服务端口
  selector:
    app: kubia

可以看到基本svc的配置非常简单,只定义了两个端口和一个选择器,我们在选择器中注明了app: kubia,意思就是让这个svc去将所有携带app:kubia标签的pod 纳入自己的后方。ports.name不是必填项,但是为了方便维护,请为每个端口指定名称。然后使用下面的命令创建svc

kubectl create -f

创建好了之后来看一下,执行kubectl get svc kubia,我们可以看到svc的信息:

NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubia   ClusterIP   10.98.237.175   <none>        80/TCP    127m

可以在custer-ip列看到当前ip地址,其他的pod 就是通过这个ip访问到其后面的pod。接下来我们随便使用一个pod 访问这个服务。

kubectl exec kubia-7rt2n -- curl -s 10.98.237.175

这条命令里的–是一个分隔符,之前的部分是属于kubectl的,之后是属于要在pod内部执行的命令。

然后可以看到来自service后方pod的响应

root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-pxfw7
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-7rt2n
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-7rt2n
root@master1:~# kubectl exec kubia-7rt2n -- curl -s 10.98.237.175 
You've hit kubia-fxqcc

我们可以看到,service同时也实现了负载均衡,合理的将请求平摊到每个pod上了。

Service对pod的动态绑定

因为svc是通过我们事先定义好的标签选择器来查找 pod 的,所以 pod 的 ip 地址变动对于svc毫无影响,其实在svc和pod之间还包含了一个资源叫做endpoint(这个我再另一篇文章里有介绍),endpoint(简称ep)是一组地址及其端口的合集,如下图,只要一个svc有标签选择器的话,他就会自动创建一个同名的ep来标记出自己的要管理的 pod。

svc, ep, pod之间的关系

我们可以通过如下的命令来查看我们刚创建的kubia服务的ep

root@master1:~# kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia
Type:              ClusterIP
IP:                10.98.237.175
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.1.18:8080,10.244.2.14:8080,10.244.3.14:8080
Session Affinity:  None
Events:            <none>

然后就可以在Endpoints列中找到他包含的地址及端口号,这三个用,分隔的地址正是三个 pod 的地址。你可以使用kubectl get pod -o wide来查看 pod 的地址。你可以执行如下命令来重建所有的kubia pod,然后再来查看ep,会发现其ep也会自动更换成这三个 pod 的值

kubectl delete po -l app=kubia

root@master1:~# kubectl get endpoints kubia
NAME    ENDPOINTS                                            AGE
kubia   10.244.1.18:8080,10.244.2.14:8080,10.244.3.14:8080   169m

服务发现

你可能已经发现了,在上文的测试中,我们使用了curl http://x.x.x.x的方式访问的svc,这样的话如果svc重建导致 ip 地址改变了,那我们岂不是访问不到了?k8s 也想到了这一点,所以提供了通过FQDN(全限定域名)访问服务的方式,你可以在任意一个 pod 上直接使用服务的名称来访问服务:

root@master1:~# k exec kubia-5n2m2 -- curl -s http://kubia
You've hit kubia-bv2k8

这种方式可以访问同一命名空间中的服务,k8s 也支持访问其他命名空间的服务。不过域名要长很多。有兴趣的话可以自行了解。

如果你发现你访问不到服务的话请使用kubectl delete po -l app=kubia重建 pod。因为 k8s 只会在创建时间晚于服务的 pod 中注入服务域名。你可以在容器中查看/etc/resolv.conf文件来找到对应的解析。

顺带一提,这个功能是 k8s 的 dns 服务器coredns实现的,你可以在命名空间kube-system中找到它。

访问集群外部的服务:ep

可以查看我的另一篇文章: https://www.clarkhu.net/?p=4614

以上就是k8s中,服务之间是如何沟通的

Docker学习之Dockerfile

首先介绍下dockerfile:Dockerfile是一份文本文档,描述了组装镜像的步骤,也就是说,只要把Dockerfile写好,然后build一下,我们的镜像就做好了。Dockerfile由指令构成,一个指令一行,build时,指令由上到下依次执行,一次执行一条,执行完后生成一个镜像层,然后下一条指令会在该基础上执行,从而产生新的镜像层。于是一层一层叠起来,我们的目标镜像就这样诞生了。

#comment
Instruction arguments

#号为注释符,有效行由两部分组成,一是指令名称,二是参数

常用指令:

  • FROM指令:Dockerfile的第一条指令,它指定了构建镜像的基础镜像
FROM
举例:FROM responsity-url/xxxx:1.2.18
  • MAINTAINER指令:指定镜像的作者
MAINTAINER
举例:MAINTAINER  clarkhu  clarkhu@qq.com
  • RUN指令:使用前一条指令创建的镜像生产容器,并在容器中执行命令,执行结束后会自动提交成为新的镜像。
    格式:RUN (shell格式 /bin/sh -c执行) 或者RUN[“executable”, “param1”, “param2”]
RUN
举例:RUN chmod +x /start
  • CMD指令:为容器提供运行的默认值,作为容器启动的默认第一条指令。在Dockerfile只能有一条,有多条的话,以最后一条为准
举例:CMD echo "Hello world"
  • EXPOSE指令:声明容器在运行时将会监听的特定端口,即对外暴露的端口。但是一般端口映射在docker run的时候用p参数指定
docker run -it -v /tmp/share:/tmp/share -p 8888:8888 --name="test" test/nginx:v3 /bin/bash
  • ENV指令:设置环境变量,作用于Dockerfile后续的所有指令。而且还会作用于生成镜像所创建出来的容器中。
举例:ENV name value
  • ADD指令:复制文件到镜像中,可以是文件,目录,甚至是url,可以是绝对路径,也可以是相对路径(相对于workdir)
举例:ADD xxxx yyy
  • COPY指令:复制文件到镜像中,和ADD的区别,COPY只能复制本地文件
举例:COPY Dockerstart  /start
  • ENTRYPOINT指令:为容器提供运行时的默认值,不同于cmd的是ENTRYPOINT只能传入指令,而CMD还可以传入参数
ENTRYPOINT ["executable", "param1", "param2"]
举例:ENTRYPOINT ["/start"]
  • WORKDIR指令:指定工作目录,设置相对路径,接下来该目录用于RUN, CMD, ENTRYPOINT, COPY和ADD指令
WORKDIR /data/www/

利用dockerfile构建镜像:

docker build -t xxx/yyy:zzz .

docker build 构建镜像的原理:以FROM指定镜像为基础。然后针对Dockerfile第一条有实际操作的指令,执行以下流程:将当前已构建的层合并起来作为镜像创建容器,执行操作,提交产生新的layer。当build完成时,我们就能得到很多layer,将它们堆叠起来,才是我们最终所产生的镜像。

  • 构建三步骤

1.编写dockerfile
2.docker build
3.docker run

  • Dockerfile指令总结
Dockerfile 过程解析
Dockerfile指令

Docker读书笔记(二) 容器

  • 启动容器

一种是基于镜像新建一个容器并启动
另一种是将在终止状态的容器重新启动

A.新建并启动

//输出一个"hello world",之后终止容器
docker run ubuntu:14.04 /bin/echo "Hello world"
Hello World
  • 1.下面的命令则启动一个bash终端,允许用户进行交互
docker run -it ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#
-t 让Docker 分配一个伪终端并绑定到容器的标准输入上
-i 让容器的标准输入保持打开

当利用docker run 来创建容器时,Docker在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个ip地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

  • 启动已终止容器

利用docker start命令,直接将一个已终止的容器启动运行

  • 守护态运行

可以通过-d参数来实现

docker run -d ubuntu:14.04 /bin/sh -c "xxxxx"

容器启动后,返回一个唯一的id,通过docker ps 来查看容器信息

  • 终止容器

可以使用docker stop终止一个运行中的容器,只启了一个终端的容器,可以通过exit或ctrl+d来退出终端,所创建的容器立刻终止。

  • 进入容器
docker attach [容器名]
  • 删除容器
docker rm [容器名]

Docker 读书笔记(一) 镜像

  • 一个没有任何父镜像镜像,称之为基础镜像
  • 镜像ID
所有镜像都是通过一个64位十六进制字符串来标识的,为简化使用,前12个字符可以组成一个短id,可以在命令行中使用。
  • 获取镜像
可以使用docker pull命令从仓库获取所需要的镜像

docker pull ubuntu:12.04

该命令实际相当于 docker pull registry.hub.docker.com/ubuntu:12.04

即从注册服务器registry.hub.docker.com中的ubuntu仓库下载标记为12.04的镜像
获取镜像后,可随时使用该镜像,例如创建一个容器,并在其中运行bash

docker run -t -i ubuntu:12.04 /bin/bash
root@fe7fc4bd8fc9:/#
  • 列出本地镜像
docker images

REPOSITORY       TAG      IMAGE ID      CREATED      VIRTUAL SIZE  
ubuntu           12.04    74fe38d11401  4 weeks ago  209.6 MB 
ubuntu           precise  74fe38d11401  4 weeks ago  209.6 MB 
ubuntu           14.04    99ec81b80c55  4 weeks ago  266 MB 
ubuntu           latest   99ec81b80c55  4 weeks ago  266 MB 
ubuntu           trusty   99ec81b80c55  4 weeks ago  266 MB 
... 

信息字段含义:

  • 来自于哪个仓库,比如ubuntu
  • 镜像的标记,比如14.04
  • 它的ID号(唯一)
  • 创建时间
  • 镜像大小

PS:如果镜像ID一致,则说明它们实际上是同一镜像,比如14.04,lastest,trusty是同一个镜像

TAG信息用来标记来自同一个仓库的不同镜像。例如ubuntu仓库中有多个镜像,通过TAG来区分发行版本,如10.04,12.04,12.10等。如果要使用ubuntu14.04来启启动容器

  • 使用镜像启动容器
docker run -it ubuntu:14.04 /bin/bash
  • 创建镜像
    1. 可以从Docker Hub获取已有镜像并更新
    2. 可以利用本地文件系统创建一个
  • 修改已有镜像
docker run -it training/sinatra /bin/bash
root@0b2616b0e5a8:/

0b2616b0e5a8 为容器ID

root@0b2616b0e5a8:/#gem install json //安装json

docker commit 命令提交更新后的副本

docker commit -m "Added json" -a "Docker Newbee" 0b2616b0e5a8 outuser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c (新的镜像id)


-m 用来指定提交说明信息, 类似git commit -m
-a 指定更新的用户信息;之后是用来创建镜像的容器ID
最后指定目标镜像的仓库名(outuser)和tag(sinatra:v2)信息。创建成功后会返回这个镜像ID信息
  • 利用Dockerfile创建镜像

使用docker commit 来扩展一个镜像比较简单,但是不方便一个团队分享。可以使用docker build 来创建一个新的镜像。为此,首先需要创建一个Dockerfile,包含一些如何创建镜像的指令。

  • 1. 新建一个目录和一个Dockfile
mkdir sinatra
cd sinatra
touch Dockfile
  • 2. Dockerfile中每一条指令都创建镜像的一层,例如:
From ubuntu:14.04
MAINTAINER Docker Newbee <newbee@docker.com>
RUN apt-get -qq update
RUN apt-get -qqy install ruby ruby-dev
RUN gem install sinatra
Dockerfile基本的语法是

1.使用#来注释
2.FROM 指令告诉Docker使用哪个镜像作为基础
3.接着是维护者的信息
4.RUN开关的指令会在创建中运行,比如安装一个软件包,在这里使用apt-get来安装一些软件
  • 3. 编写Dockerfile后可以使用docker build 来生成镜像
docker build -t="ouruser/sinatra:v2"

-t 标记来添加tag,指定新的镜像的用户信息。
“.” 是Dockerfile所在的路径(当前目录),也可以替换为一个具体的Dockerfile的路径。

docker build 进程在执行操作,它要做的第一件事就是上传这个Dockerfile内容,因为所有的操作都要依据Dockerfile来进行。然后,Dockerfile中指令被一条一条的执行。每一步都创建了一个新容器,在容器中执行指令并提交修改(就跟之前介绍过的docker commit一样)。当所有的指令都执行完毕之后,返回了最终的镜像id。所有的中间步骤所产生的容器都被删除和清理了。

PS: 注意一个镜像不能超过127层

Dockerfile 命令说明:
1. ADD 命令复制本地文件到镜像;
2. 用EXPOSE命令来向外开放端口;
3. 用CMD命令来描述容器启动后运行的程序等。

  • docker tag 命令来修改镜像的标签
  • 上传镜像
docker push ouruser/sinatra  把自己创建的镜像上传到仓库中来共享
  • docker save 导出镜像到本地文件
  • 载入镜像 docker load 从导出的本地文件中再导入到本地镜像库
  • 移出本地镜像 docker rmi
  • 镜像的实现原理
每个docker镜像都由很多层构成,Docker使用Union FS将这些不同的层结合到一个镜像中去

通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。 Docker 在 AUFS 上构建的容器也是利用了类似的原理
  • 镜像仓库地址设置
国内访问 Docker 的官方仓库很慢,还经常断线,所以要把仓库网址改成国内的镜像站。这里推荐使用官方镜像 registry.docker-cn.com

Docker中国官方镜像加速
--registry-mirror=https://registry.docker-cn.com
网易163镜像加速
--registry-mirror=http://hub-mirror.c.163.com
中科大镜像加速
--registry-mirror=https://docker.mirrors.ustc.edu.cn
阿里云镜像加速
--registry-mirror=https://{your_id}.mirror.aliyuncs.com
daocloud镜像加速
--registry-mirror=http://{your_id}.m.daocloud.io 

1.创建目录 mkdir -p /etc/docker
2.编辑/etc/docker/daemon.json文件,并输入国内镜像源地址
eg:

{
    "registry-mirrors": ["https://registry.docker-cn.com"]
}