容器化中的k8s技术

56 容器化技术
虚拟机和容器对比:
技术:虚拟机依赖的是 KVM,容器依赖的是 namespace 和 cgroup 对进程进行隔离。
虚拟机更重,需要虚拟出客户机的cpu、内存、存储、网络等资源;
容器则很轻量,直接使用宿主机的各种资源。
虚拟机的隔离性更高,容器则较低。
效率上容器更高。


容器用到了什么技术?
1)namespace,隔离,每个namespace中的应用看到的,都是不同的IP地址、用户空间、进程ID等、
2)cgroup,资源限制,,即明明整台机器有很多的 CPU、内存,但是一个应用只能用其中的一部分。

容器带来了什么好处?
资源的隔离和限制,避免了服务之间的相互影响。
镜像实现的搭积木方式,持续集成和跨云迁移很方便。
可以实现资源的弹性迁移

无论是容器,还是虚拟机,都依赖于内核中的技术,虚拟机依赖的是 KVM,容器依赖的是 namespace 和 cgroup 对进程进行隔离。

57 namespace技术
namespace的类型
UTS,对应的宏为 CLONE_NEWUTS,不同的 namespace 可以配置不同的 hostname。
User,对应的宏为 CLONE_NEWUSER,不同的 namespace 可以配置不同的用户和组。
Mount,对应的宏为 CLONE_NEWNS,不同的 namespace 的文件系统挂载点是隔离的
PID,对应的宏为 CLONE_NEWPID,不同的 namespace 有完全独立的 pid,也即一个 namespace 的进程和另一个 namespace 的进程,pid 可以是一样的,但是代表不同的进程。
Network,对应的宏为 CLONE_NEWNET,表示不同的 namespace 有独立的网络协议栈。

进程号验证
假设用docker起了一个nginx进程

docker ps
获取容器Id f604f0e34bc2

docker inspect containerId
获取pid: 即State.Pid对应的值

ps查看机器上nginx的进程:
ps -ef | grep nginx,结果如下:
# ps -ef |grep nginx
root     58212 58195  0 01:43 ?        00:00:00 /bin/sh -c nginx -g "daemon off;"
root     58244 58212  0 01:43 ?        00:00:00 nginx: master process nginx -g daemon off;
33       58250 58244  0 01:43 ?        00:00:00 nginx: worker process
33       58251 58244  0 01:43 ?        00:00:05 nginx: worker process
33       58252 58244  0 01:43 ?        00:00:05 nginx: worker process
33       58253 58244  0 01:43 ?        00:00:05 nginx: worker process

在 /proc/pid/ns 里面,我们能够看到这个进程所属于的 6 种 namespace。我们拿出两个进程来,应该可以看出来,它们属于同一个 namespace。
代码如下:
# ls -l /proc/58212/ns 
lrwxrwxrwx 1 root root 0 Jul 16 19:19 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 root root 0 Jul 16 01:43 net -> net:[4026532281]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 pid -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 uts -> uts:[4026532277]

# ls -l /proc/58253/ns 
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 net -> net:[4026532281]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 pid -> pid:[4026532279]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 user -> user:[4026531837]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 uts -> uts:[4026532277]


操作namespace的常用指令
nsenter:可以用来运行一个进程,进入指定的namespace
如,通过下面的命令,我们可以运行 /bin/bash,并且进入 nginx 所在容器的 namespace。
# nsenter --target 58212 --mount --uts --ipc --net --pid -- env --ignore-environment -- /bin/bash

unshare:离开当前的 namespace,创建且加入新的 namespace,然后执行参数中指定的命令。
如,运行下面这行命令之后,pid 和 net 都进入了新的 namespace。
unshare --mount --ipc --pid --net --mount-proc=/proc --fork /bin/bash

运行上面的指令后:pid和net都进入了新的 namespace,所以我们查看进程列表和 ip 地址的时候应该会发现有所不同。


通过函数操作namespace:
clone 创建一个新的进程,并把它放到新的 namespace 中。
setns 将当前进程加入到已有的 namespace 中。
unshare 使当前进程退出当前的 namespace,并加入到新创建的 namespace。

clone 和 unshare 的区别是,unshare 是使当前进程加入新的 namespace;clone 是创建一个新的子进程,然后让子进程加入新的 namespace,而当前进程保持不变。

clone函数进入一个namespace
调用 clone 的时候,给的参数是 CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWNET,也就是说,我们会进入一个新的 pid、network,以及 mount 的 namespace。
在内核里面,clone 会调用 _do_fork->copy_process->copy_namespaces,也就是说,在创建子进程的时候,有一个机会可以复制和设置 namespace。

每一个进程的 task_struct 里面,有一个指向 namespace 结构体的指针 nsproxy。
struct task_struct {
......
  /* Namespaces: */
  struct nsproxy      *nsproxy;
......
}

/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 */
struct nsproxy {
  atomic_t count;
  struct uts_namespace *uts_ns;
  struct ipc_namespace *ipc_ns;
  struct mnt_namespace *mnt_ns;
  struct pid_namespace *pid_ns_for_children;
  struct net        *net_ns;
  struct cgroup_namespace *cgroup_ns;
};


copy_namespaces 的实现
/*
 * called from clone.  This now handles copy for nsproxy and all
 * namespaces therein.
 */
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{
  struct nsproxy *old_ns = tsk->nsproxy;
  struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
  struct nsproxy *new_ns;

  if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
            CLONE_NEWPID | CLONE_NEWNET |
            CLONE_NEWCGROUP)))) {
    get_nsproxy(old_ns);
    return 0;
  }

  if (!ns_capable(user_ns, CAP_SYS_ADMIN))
    return -EPERM;
......
  new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);

  tsk->nsproxy = new_ns;
  return 0;
}

接着会调create_new_namespaces,里面主要是对各种namespace的复制。
如 copy_pid_ns 对于 pid namespace 的复制
struct pid_namespace *copy_pid_ns(unsigned long flags,
  struct user_namespace *user_ns, struct pid_namespace *old_ns)
{
  if (!(flags & CLONE_NEWPID))
    return get_pid_ns(old_ns);
  if (task_active_pid_ns(current) != old_ns)
    return ERR_PTR(-EINVAL);
  return create_pid_namespace(user_ns, old_ns);
}
在 copy_pid_ns 中,如果没有设置 CLONE_NEWPID,则返回老的 pid namespace;如果设置了,就调用 create_pid_namespace,创建新的 pid namespace.

总结:
namespace 相关的技术,有六种类型,分别是 UTS、User、Mount、Pid、Network 和 IPC。
还有两个常用的命令 nsenter 和 unshare,主要用于操作 Namespace,有三个常用的函数 clone、setns 和 unshare。

在内核里面,对于任何一个进程 task_struct 来讲,里面都会有一个成员 struct nsproxy,用于保存 namespace 相关信息,里面有 struct uts_namespace、struct ipc_namespace、struct mnt_namespace、struct pid_namespace、struct net *net_ns 和 struct cgroup_namespace *cgroup_ns。

创建 namespace 的时候,我们在内核中会调用 copy_namespaces,调用顺序依次是 copy_mnt_ns、copy_utsname、copy_ipcs、copy_pid_ns、copy_cgroup_ns 和 copy_net_ns,来复制 namespace。

58 cgroup技术
cgroup主要是用于控制资源的使用。

cgroup 定义了下面的一系列子系统,每个子系统用于控制某一类资源。
CPU 子系统,主要限制进程的 CPU 使用率。
cpuacct 子系统,可以统计 cgroup 中的进程的 CPU 使用报告。
cpuset 子系统,可以为 cgroup 中的进程分配单独的 CPU 节点或者内存节点。
memory 子系统,可以限制进程的 Memory 使用量。
blkio 子系统,可以限制进程的块设备 IO。
devices 子系统,可以控制进程能够访问某些设备。
net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
freezer 子系统,可以挂起或者恢复 cgroup 中的进程。

运行指令如下:
docker run -d --cpu-shares 513 --cpus 2 --cpuset-cpus 1,3 --memory 1024M --memory-swap 1234M --memory-swappiness 7 -p 8081:80 testnginx:1

docker ps获取容器id为:3dc0601189dd

cgroup在用户态对于Docker资源的控制如下图所示:


那在内核中cgroup是如何实现的呢?
第一步,系统初始化的时候,初始化 cgroup 的各个子系统的操作函数,分配各个子系统的数据结构。
第二步,mount cgroup 文件系统,创建文件系统的树形结构,以及操作函数。
第三步,写入 cgroup 文件,设置 cpu 或者 memory 的相关参数,这个时候文件系统的操作函数会调用到 cgroup 子系统的操作函数,从而将参数设置到 cgroup 子系统的数据结构中。
第四步,写入 tasks 文件,将进程交给某个 cgroup 进行管理,因为 tasks 文件也是一个 cgroup 文件,统一会调用文件系统的操作函数进而调用 cgroup 子系统的操作函数,将 cgroup 子系统的数据结构和进程关联起来。
第五步,对于 CPU 来讲,会修改 scheduled entity,放入相应的队列里面去,从而下次调度的时候就起作用了。对于内存的 cgroup 设定,只有在申请内存的时候才起作用。
 

59 从单个操作系统到集群的k8s
操作系统最重要的事情,就是调度。将程序员从对硬件的直接操作中解放出来,提升程序设计的效率。
因此,在内核态就产生了这些模块:进程管理子系统、内存管理子系统、文件子系统、设备子系统和网络子系统。

当我们面临数据中心成千上万台机器的时候,天天关心哪个程序放在了哪台机器上,使用多少内存、多少硬盘,每台机器总共有多少内存、多少硬盘,还剩多少内存和硬盘,那头就大了。这时有一个对物理资源统一管理的调度器---k8s。

Kubernetes 作为数据中心的操作系统还是主要管理数据中心里面的四种硬件资源:CPU、内存、存储、网络。
CPU 和内存这两种计算资源的管理,我们可以通过 Docker 技术完成。
存储方面有以下几种方式:
1)对象存储。将文件作为一个完整对象的方式来保存。每一个文件对我们来说,都应该有一个唯一标识这个对象的 key,而文件的内容就是 value。
2)分布式文件系统。通过网络的方式访问远程的文件系统。
3)分布式块存储。这就相当于云硬盘,也即存储虚拟化的方式,只不过将盘挂载给容器而不是虚拟机。

接下来是网络。因为不同的服务器上的 Docker 还是需要互相通信的。
Kubernetes 有自己的网络模型,,里面的每一个 Docker 访问另一个 Docker 的时候,都是感觉在一个扁平的网络里面。
要实现这样的网络模型,有很多种方式,例如 Kubernetes 自己提供 Calico、Flannel。
当然,也可以对接 Openvswitch 这样的虚拟交换机,也可以使用 brctl 这种传统的桥接模式,也可以对接硬件交换机。

看,这又是一种类似驱动的模式,和操作系统面临的问题是一样的。Kubernetes 同样是提供统一的接口 Container Network Interface(CNI,容器网络接口)。无论你用哪种方式实现网络模型,只要对接这个统一的接口,Kubernetes 就可以管理容器的网络。

至此,Kubernetes 作为数据中心的操作系统,内核的问题解决了。接下来是用户态的工作模式问题了。我们能不能像操作一台服务器那样操作数据中心呢?
第一种进程是交互式命令行,运行起来就是执行一个任务,结束了马上返回结果。在 Kubernetes 里面有对应的概念叫作 Job,
第二种进程是 nohup(长期运行)的进程。在 Kubernetes 里对应的概念是 Deployment,让某个进程以 N 的 Pod 副本的形式运行,并且长期运行。
第三种进程是系统服务。在 Kubernetes 里面对应的概念是 DaemonSet,它保证在每个节点上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用。
第四种进程是周期性进程,也即 Crontab,常常用来设置一些周期性的任务。

存储来讲,Kubernetes 有 Volume 的概念。Volume 的生命周期与 Pod 绑定在一起,容器挂掉后,Kubelet 再次重启容器时,Volume 的数据依然还在,而 Pod 删除时,Volume 才会真的被清理。

对于网络来讲,Kubernetes 有自己的 DNS,有 Service 的概念。Kubernetes 会将 Service 的名字作为域名解析成为一个虚拟的 Cluster IP,然后通过负载均衡,转发到后端的 Pod。虽然 Pod 可能漂移,IP 会变,但是 Service 会一直不变。

对应到 Linux 操作系统的 iptables,Kubernetes 在有个概念叫 Network Policy,Network Policy 提供了基于策略的网络控制,用于隔离应用并减少攻击面。它使用标签选择器模拟传统的分段网络,并通过策略控制它们之间的流量以及来自外部的流量。


linux操作系统和k8s数据中心操作系统对比图,如下图:

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值