├── .nojekyll ├── 01~虚拟化 ├── 存储虚拟化 │ └── README.md ├── Docker │ ├── 02~镜像与容器 │ │ ├── README.md │ │ ├── 99~参考资料 │ │ │ └── 2024~Understanding Container Image Layers.md │ │ ├── 镜像体积优化.md │ │ └── Dockerfile 配置.md │ ├── 10~Docker 内部原理 │ │ └── .gitkeep │ ├── 01~配置部署 │ │ ├── README.md │ │ ├── Docker Compose.md │ │ ├── 安装与配置.md │ │ └── 容器编排.md │ ├── .DS_Store │ ├── 99~参考资料 │ │ ├── 2020-《Docker 从入门到实践》 │ │ │ └── README.md │ │ └── 2023-无限咪咪-Docker 与 Kubernetes小记.md │ ├── 04~网络 │ │ ├── README.md │ │ ├── 网络模型.md │ │ └── 网络配置.md │ ├── README.md │ ├── 03~数据卷与存储 │ │ └── README.md │ └── 09~Swarm │ │ ├── Swarm.md │ │ └── 基于 Docker Swarm 的微服务编排与监控.md ├── 容器 │ ├── 云厂商的容器技术.md │ ├── 容器技术简史.md │ └── README.md ├── Focker │ ├── Bash.md │ ├── C.md │ ├── Python.md │ ├── README.md │ └── Go.md ├── Linux 资源隔离 │ ├── 虚拟内核.md │ ├── LXC.md │ ├── README.md │ ├── CGroups.md │ ├── AUFS.md │ └── Namespaces.md ├── .DS_Store ├── Xen │ ├── README.md │ ├── 快速开始.md │ └── 虚拟机创建.md ├── OCI │ └── README.md ├── Ceph │ └── README.md └── README.md ├── INTRODUCTION.md ├── 02~集群编排 ├── Yarn │ └── README.md ├── .DS_Store ├── 资源调度 │ ├── README.md │ ├── 计算调度.md │ ├── 任务调度.md │ ├── 单机资源管理.md │ └── 数据调度.md ├── README.md ├── 云上安全 │ └── README.md ├── OAM │ ├── 协议规范.md │ └── README.md ├── 云计算.md └── Mesos │ ├── 部署与配置.md │ └── README.md ├── .DS_Store ├── 03~K8s └── README.md ├── .gitattributes ├── .gitignore ├── 10~边缘计算 ├── README.md └── 开源项目梳理.md ├── README.md ├── _sidebar.md ├── index.html └── LICENSE /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~虚拟化/存储虚拟化/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /INTRODUCTION.md: -------------------------------------------------------------------------------- 1 | # 本篇导读 2 | -------------------------------------------------------------------------------- /02~集群编排/Yarn/README.md: -------------------------------------------------------------------------------- 1 | # Yarn 2 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/02~镜像与容器/README.md: -------------------------------------------------------------------------------- 1 | # 镜像 -------------------------------------------------------------------------------- /01~虚拟化/Docker/10~Docker 内部原理/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~虚拟化/容器/云厂商的容器技术.md: -------------------------------------------------------------------------------- 1 | # 云厂商的容器技术 2 | -------------------------------------------------------------------------------- /01~虚拟化/Focker/Bash.md: -------------------------------------------------------------------------------- 1 | # Focker in Bash 2 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/01~配置部署/README.md: -------------------------------------------------------------------------------- 1 | # Docker 配置与部署 2 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/01~配置部署/Docker Compose.md: -------------------------------------------------------------------------------- 1 | # Docker Compose 2 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/虚拟内核.md: -------------------------------------------------------------------------------- 1 | # 虚拟内核 2 | 3 | Docker 等容器解决方案只是 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/Cloud-Notes/master/.DS_Store -------------------------------------------------------------------------------- /01~虚拟化/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/Cloud-Notes/master/01~虚拟化/.DS_Store -------------------------------------------------------------------------------- /01~虚拟化/Xen/README.md: -------------------------------------------------------------------------------- 1 | # Xen 2 | 3 | # Links 4 | 5 | - https://blog.51cto.com/wzlinux/1727106 6 | -------------------------------------------------------------------------------- /02~集群编排/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/Cloud-Notes/master/02~集群编排/.DS_Store -------------------------------------------------------------------------------- /01~虚拟化/Focker/C.md: -------------------------------------------------------------------------------- 1 | # Focker in C 2 | 3 | # Links 4 | 5 | - https://zserge.com/posts/containers/ 6 | -------------------------------------------------------------------------------- /03~K8s/README.md: -------------------------------------------------------------------------------- 1 | # K8s 2 | 3 | 本篇迁移到了 [K8s-Notes](https://github.com/wx-chevalier/K8s-Notes) 4 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/Cloud-Notes/master/01~虚拟化/Docker/.DS_Store -------------------------------------------------------------------------------- /01~虚拟化/容器/容器技术简史.md: -------------------------------------------------------------------------------- 1 | # 容器技术简史 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/0nq0EecNDpFxmkNN-vKKzg 6 | -------------------------------------------------------------------------------- /01~虚拟化/Focker/Python.md: -------------------------------------------------------------------------------- 1 | # Focker in Python 2 | 3 | # Links 4 | 5 | - https://github.com/Fewbytes/rubber-docker 6 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/99~参考资料/2020-《Docker 从入门到实践》/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://yeasy.gitbook.io/docker_practice/introduction/what) 2 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/04~网络/README.md: -------------------------------------------------------------------------------- 1 | # 网络 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/Jdxct8qHrBUtkUq-hnxSRw 浅聊几种主流 Docker 网络的实现原理 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xmind filter=lfs diff=lfs merge=lfs -text 2 | *.zip filter=lfs diff=lfs merge=lfs -text 3 | *.pdf filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /02~集群编排/资源调度/README.md: -------------------------------------------------------------------------------- 1 | # 分布式调度 2 | 3 | 典型的分布式系统从经典的资源调度器(譬如 Yarn),慢慢扩展为了包含数据调度(Data Placement)、资源(任务)调度(Resouce Management)、计算调度(Application Manager)、和本地微(自治)调度等多个领域。 4 | -------------------------------------------------------------------------------- /01~虚拟化/Focker/README.md: -------------------------------------------------------------------------------- 1 | # Focker,从零实现的简易 Docker 2 | 3 | 在本节中,我们会通过 Bash, C, Go, Python 等不同的语言来实现简易的 Docker。 4 | 5 | > 本部分的示例代码参考 [focker](https://github.com/BE-Kits/focker) 6 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/02~镜像与容器/99~参考资料/2024~Understanding Container Image Layers.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://www.kenmuse.com/blog/understanding-container-image-layers/) 2 | 3 | # Understanding Container Image Layers 4 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/LXC.md: -------------------------------------------------------------------------------- 1 | # LXC 2 | 3 | LXC 是一种操作系统级虚拟化方法,在执行时不用重复加载内核, 且其内核与宿主共享,允许其他一些沙盒进程运行在一块相对独立的空间,并且能够方便的控制他们的资源调度。 4 | 5 | # Links 6 | 7 | - https://mp.weixin.qq.com/s/cnr_J9u_vjrZJiQSktd_xQ 8 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/02~镜像与容器/镜像体积优化.md: -------------------------------------------------------------------------------- 1 | # 镜像体积优化 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/kyK6652kchtudZHhSsYx_Q 两个奇技淫巧,将 Docker 镜像体积减小 99% 6 | - https://contains.dev/blog/optimizing-docker-image-size 7 | -------------------------------------------------------------------------------- /02~集群编排/资源调度/计算调度.md: -------------------------------------------------------------------------------- 1 | # 计算调度 2 | 3 | 每个 Job 抽象成一个 DAG(有向无环图),图中的节点有前后依赖关系。随着阿里大数据业务的增长和新计算模型的提出,DAG 框架需要更好的动态性,以更灵活的适应数据和资源的变化。此外,计算调度和 Shuffle 系统需要对不同规模都给出最优的调度效果和执行性能。 4 | 5 | 业界各个分布式系统(包括 SPARK, FLINK, HIVE, SCOPE, TENSORFLOW)都包含 DAG 执行框架,这些执行框架的本源都可以归结于 Dryad 提出的 DAG 模型。目前业内的 DAG 执行框架都依赖于特定的分布式系统,要么缺少清晰的点,边,图的定义,要么缺少动态执行调整能力,很难用一套 DAG 执行框架来满足大数据所有计算场景的需要。 6 | -------------------------------------------------------------------------------- /01~虚拟化/OCI/README.md: -------------------------------------------------------------------------------- 1 | # OCI 2 | 3 | 为了防止容器被 Docker 一家垄断,巨头们(谷歌,Redhat、微软、IBM、Intel、思科)决定要成立一个组织(OCI),大家一起商量指定了一套规范(CRI、CNI),大家一致统一只兼容符合这套规范的工具。 4 | 5 | 他们主要是由 RedHat 推动,三者各司其职,配合完成 Docker 所有的功能和新扩展功能,并且对 docker 的问题进行了改良:包括不需要守护程序或访问有 root 权限的组;容器架构基于 fork/exec 模型创建容器,更加安全可靠;所以是更先进、高效和安全的下一代容器容器工具。 6 | 7 | # Links 8 | 9 | - 再见 Docker,Podman、Skopeo 和 Buildah 下一代容器新架构强势出击! https://zhuanlan.zhihu.com/p/77373246 10 | -------------------------------------------------------------------------------- /02~集群编排/资源调度/任务调度.md: -------------------------------------------------------------------------------- 1 | # 资源调度 2 | 3 | 群内的海量硬件资源,如 CPU、内存、磁盘、网络、GPU、FPGA 等,需要快速地分配给每天上千万的 job,几十亿的计算实例。既要满足 CPU 密集、内存密集、或者某种型号的 GPU 板卡、数据 locality、多租户配额等多种资源约束,同时又要优化集群的资源利用率,削峰填谷、取长补短、降低成本。尤其随着集群规模的急剧扩大,这些问题的解决需要架构上有新的突破。 4 | 5 | 经过几十年的发展,YARN 和 Kubernetes 成为代表性的开源调度框架。YARN 提出的双层调度框架实现了资源管理和调度的分离,满足了中小规模离线作业频繁调度的需求,但在超大规模场景下调度性能存在不足,集群利用率不高,多租户间的资源公平性较差;而 Kubernetes 是面向容器场景的调度(容器只要一次启动、不需要频繁调度),主要解决容器的编排、管理等问题,更适合任务长时间运行的场景,但在大数据计算高并发作业的场景,没有有效的解决方案。 6 | -------------------------------------------------------------------------------- /01~虚拟化/Xen/快速开始.md: -------------------------------------------------------------------------------- 1 | # Start 2 | 3 | ## AutoStart 4 | 5 | 在 Critix 6.0 之后将单个虚拟机的自启动功能去掉了,不过我们可以通过 TAG 管理的方式配置多个虚拟机自动启动,可惜这种方式不能支持虚拟机启动顺序的指定。首先我们需要 XenCenter 里面选中 vm---Properties---General--Tags--Edit Tags,比如输入 autostart 作为 Tag 的值,给所有需要自动启动的虚拟机都做同样的打标机操作,然后用 SSH 工具连到 XenServer, 用 vi 编辑文件 /etc/rc.d/rc.local,在文件末尾添加两行内容: 6 | 7 | ```sh 8 | sleep 60 9 | xe vm-start tags=autostart --multiple 10 | ``` 11 | 12 | 注意 multiple 前面是两个中杠,tags=autostart 和 Step1 保持一样,保存并退出,下次启动 Xenserver 就会发现打了 Tas 的 vm 自动启动。 13 | -------------------------------------------------------------------------------- /02~集群编排/资源调度/单机资源管理.md: -------------------------------------------------------------------------------- 1 | # 单机资源管理 2 | 3 | 大量的任务实例在物理机器上实际运行时,需要单机上的隔离保护机制,以有效保障不同任务对物理资源的需求,确保高低优先级不要互相影响。同时还需要保护物理机器,避免进入过载状态,保障整机的可用性。资源高压力下的 SLA 保障一直以来是学术界和工业界发力的方向,诸如 Borg、Heracles、autoscaler 等开源探索,都假设在资源冲突时,无条件向在线业务倾斜,离线业务可以随时被牺牲。但是,离线内部的 AppMaster、实时、准实时等业务也有强 SLA 需求,不能随意牺牲。对此,学术界和工业界尚无深入思考和实践,我们需要摸着石头过河,创造适合我们自己的道路。 4 | 5 | 传统的基于磁盘文件的 Shuffle 方式,在机械硬盘上有明显的碎片读问题,严重影响性能与稳定性。Spark External Shuffle Service 虽然做了一些改进,但是引入了很大的 overhead,在部分作业上得不偿失。一些工业界和学术界的工作提出了通过聚合 Partition 数据的方式达到极致性能,但缺少异常状况下的处理方案,稳定性有很大缺失。 6 | -------------------------------------------------------------------------------- /02~集群编排/README.md: -------------------------------------------------------------------------------- 1 | # 容器编排 2 | 3 | Mesos 则更善于构建一个可靠的平台,用来运行多任务关键工作负载,包括 Docker 容器、遗留应用程序(如 Java)和分布式数据服务(如 Spark、Kafka、Cassandra、Elastic)。Mesos 采用两级调度的架构,开发人员可以很方便地结合公司的业务场景定制 Mesos Framework。 4 | 5 | 其实无论是 Kubernetes 还是 Mesos,它们都不是专门为了容器而开发的。Mesos 早于 Docker 出现,而 Kubernetes 的前身 Borg 更是早已出现,它们都是基于解除资源与应用程序本身的耦合限制而开发的。运行于容器中的应用,其轻量级的特性恰好能够与编排调度系统完美结合。 6 | 7 | 唯一为了 Docker 而生的编排系统是 Swarm,它由 Docker 所在的 Moby 公司出品,用于编排基于 Docker 的容器实例。不同于 Kubernetes 和 Mesos,Swarm 是面向 Docker 容器的,相较于 Kubernetes 面向云原生 PaaS 平台,以及 Mesos 面向“大数据+编排调度”平台,Swarm 显得功能单一。在容器技术本身已不是重点的今天,编排能力和生态规划均略逊一筹的 Swarm 已经跟不上前两者的脚步。 8 | -------------------------------------------------------------------------------- /01~虚拟化/Focker/Go.md: -------------------------------------------------------------------------------- 1 | # Focker in Go 2 | 3 | 这里我们参考 [Gocker](https://unixism.net/2020/06/containers-the-hard-way-gocker-a-mini-docker-written-in-go/) 的实现,实现我们的 Focker。 4 | 5 | ![Gocker Screenshot](https://s1.ax1x.com/2020/06/18/NeRI7q.png) 6 | 7 | 用 Gocker 创建的容器拥有自己的以下命名空间(请参见`run.go`和`network.go`): 8 | 9 | - File system (via `chroot`) 10 | - PID 11 | - IPC 12 | - UTS (hostname) 13 | - Mount 14 | - Network 15 | 16 | 当创建 cgroups 来限制以下内容时,除非您在 `gocker run` 命令中指定了 `--mem`,`--cpus` 或 `--pids` 选项,否则接续器将使用无限的资源。这些标志分别限制了容器可以使用的最大 RAM,CPU 内核和 PID。 17 | 18 | - Number of CPU cores 19 | - RAM 20 | - Number of PIDs (to limit processes) 21 | 22 | # Namespaces basics 23 | 24 | # Links 25 | 26 | - https://unixism.net/2020/06/containers-the-hard-way-gocker-a-mini-docker-written-in-go/ 27 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | Docker 是最广泛使用的容器技术,也是大多数人在引用容器时的意思。虽然还有其他开源容器技术(如 CoreOS 的 rkt)和大型公司构建自己的容器引擎(如谷歌的 lmctfy),但 Docker 已成为容器化的行业标准。它仍然建立在 Linux 内核和最近的 Windows 提供的 cgroups 和命名空间之上。 4 | 5 | # 镜像与容器 6 | 7 | Docker 容器由多层镜像组成,二进制文件一起打包到一个包中。基本镜像包含容器的操作系统,该操作系统可以与主机的操作系统不同。容器的操作系统是镜像形式。这不是主机上的完整操作系统,不同之处在于镜像只是操作系统的文件系统和二进制文件,而完整的操作系统包括文件系统,二进制文件和内核。在基础镜像的顶部是多个镜像,每个镜像构建容器的一部分。例如,在基本镜像的顶部可以是包含 apt-get 依赖性的镜像。最重要的可能是包含应用程序二进制文件的镜像,依此类推。 8 | 9 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230510140450.png) 10 | 11 | 由哈希标识的每个镜像只是构成容器的许多可能镜像层之一。但是,容器仅由其顶级镜像标识,该镜像具有对父镜像的引用。此处显示的两个顶级镜像(镜像 1 和镜像 2)共享前三个图层。镜像 2 具有两个附加的配置相关层,但与镜像 1 共享相同的父镜像。引导容器时,将从 repo 下载镜像及其父镜像,创建 cgroup 和命名空间,并使用该镜像创建虚拟环境。在容器内,镜像中指定的文件和二进制文件似乎是整个计算机中的唯一文件。然后启动容器的主进程,并将容器视为活动状态。 12 | 13 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230510140502.png) 14 | -------------------------------------------------------------------------------- /02~集群编排/云上安全/README.md: -------------------------------------------------------------------------------- 1 | # 云上安全 2 | 3 | 云上安全问题本质上都是由线下传统安全问题衍生而来的,但由于云计算平台的相对开放性又引入了新的安全风险。例如,虚拟机逃逸造成新的安全威胁,原本封闭的 IDC 需要开放新的通道而造成防护边界模糊,本地的身份认证系统与云上集成的风险,云产品配置错误或云账号 AccessKey 使用不当导致的数据泄漏风险,因缺乏专业云安全运营人员导致云上安全防护形同虚设等风险。 4 | 5 | # 云上与云下安全区别 6 | 7 | - 交付形态变化:从基于传统本地的安全开发、运维和测试到基于云弹性扩展的构建,业务数据和应用从基于传统 IDC 设施到基于云计算基础设施和云产品生态构建。开发、测试、运维、安全和交付模式均在快速变化。组织的数据从原本封闭的单一数据中心扩散到云计算中心。上云后,用户将通过基于云原生的安全产品和云产品安全能力实现基础安全,用户将更加关注交付本身的安全,而不是大量基础安全工作。 8 | 9 | - 安全边界消失:传统 IT 系统相对封闭,对外暴露的只有少数应用服务(官网、邮件、OA 服务器等)接口,传统 IT 系统以“边界”为核心,利用防火墙、入侵防御等手段可以有效阻挡攻击。而在云计算环境下,基于物理安全边界的防护机制难以在云计算环境中得到有效的应用,静态的安全防护手段作用被削弱,传统的数据中心如异构防火墙等形态消失,安全防护措施主要基于云上安全产品和云产品自身安全防护措施进行动态调整。 10 | 11 | - 数据权限分离:云计算将资源和数据的所有权、管理权和使用权进行了分离,资源和数据不在本地存储,数据的所有权仍是最终用户,但管理权和使用权根据责任分担模型进行了分工。例如,大部分情况下对数据和资源的直接控制措施(物理、逻辑、人员、隔离措施)将由云计算服务提供商负责,用户将更加专注于数据自身的安全防护。管理者应更关注如何利用云上优势(弹性扩展、按需使用、投入产出比(ROI)高、部署快、云安全产品和安全手段丰富等),快速搭建满足业务发展的安全保障措施,使得传统线下企业能够享受云安全带来的技术红利,提升传统企业安全防护水平。 12 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/03~数据卷与存储/README.md: -------------------------------------------------------------------------------- 1 | # Docker 数据卷与存储 2 | 3 | 在我们使用 Docker 容器时,为了持久化数据或多容器共享数据,我们可以在 Dockerfile 中通过 VOLUME 关键字声明容器要持久化数据的目录列表,或者通过: 4 | 5 | ```sh 6 | $ docker create/run --name containername --volume volumename ... 7 | ``` 8 | 9 | 在创建或运行容器时添加存储卷,以上两种方式创建的 volume 在 docker 中叫做 anonymous volume, 主要用在 volume 不需要多容器共享的场景(当然也可以通过--volumes-from 参数来共享)。而为了在容器层面对接不同的存储系统,docker 定义了一套 volume plugin 机制。简单来说就是 docker engine 抽象了一套 REST API 并作为 client 端,各种存储系统通过实现该套 API 作为 server 端来对接真正的存储系统,然后可以通过下面的命令来使用: 10 | 11 | ```sh 12 | # 通过driver指定创建volume的类型,opt指定创建存储所需参数,docker engine会通过driver类型将创建存储的请求forward到同node上的相应的volume plugin来真实执行创建存储的动作 13 | $ docker volume create --driver=xxx volumename --opt key1=value1 --opt key2=value2 14 | $ docker run -it --name container1 --volume volumename:/data busybox sh 15 | $ docker run -it --name container2 --volume volumename:/data ubuntu sh 16 | ``` 17 | 18 | 上面这种方式创建的 volume 也叫做 named volume,volume 的创建和容器的创建是解耦的,在多容器共享存储时语义更清晰明了,而且可以插件机制来丰富持久化存储的存储类型。 19 | -------------------------------------------------------------------------------- /02~集群编排/OAM/协议规范.md: -------------------------------------------------------------------------------- 1 | # 协议规范 2 | 3 | # 应用组件(Components) 4 | 5 | 在 OAM 中,“应用”是由多个概念共同组合而成的。第一个概念是:应用组件(Components),它是整个应用的重要组成部分。所以说,应用组件既可以包括应用运行所依赖的服务:比如 MySQL 数据库,也包括应用服务本身:比如拥有多个副本的 PHP 服务器。开发者可以把他们写的代码”打包“成一个应用组件,然后编写配置文件来描述该组件与其他服务之间的关系。 6 | 应用组件的概念,让平台架构师能够将应用分解成一个个可被复用的模块,这种模块化封装应用组成部分的思想,代表了一种构建安全、高可扩展性应用的最佳实践:它通过一个完全分布式的架构模型,实现了应用组件描述和实现的解耦。 7 | 8 | # 应用部署配置文件(Application Configuration) 9 | 10 | 而为了将这些应用组件描述变成一个真正运行起来的应用,应用运维人员会通过一个专门的、包含了所有应用组件信息的部署配置文件来实例化这个待运行的应用。这个配置文件本身也是 OAM 规范中的一个声明式 API,用来让应用运维人员能够根据开发者或者平台提交的应用描述,实例化出对应的、真正运行起来的应用。 11 | 12 | # 应用运维特征(Traits) 13 | 14 | 最后一个概念是一组应用运维特征(Traits),它们描述了应用在具体部署环境中的运维特征,比如应用的水平扩展的策略和 Ingress 规则,这些特征对于应用的运维来说非常重要,但它们在不同的部署环境里却往往有着截然不同的实现方式。 15 | 16 | 举一个简单例子,同样是 Ingress,它在公有云上和本地数据中心的实现可能是完全不同的:前者一般是 SLB 这样的云服务,而后者则可能是一个专门的硬件。这也就意味着针对这两个环境的 Ingress 运维工作,将会有天壤之别。但与此同时,无论是在哪个环境里,这个 Ingress 规则对于应用开发人员来说,可能是完全相同的。 17 | 18 | 应用特征的设计,让这种关注点分离成为可能:只要这两个环境在 OAM 模型下提供了对 Ingress 这个应用运维特征的实现,那么你的应用就可以使用统一的 Ingress 规则描述无差别的在这两个地方运行起来。而与此同时,这两个环境的基础设施供应商可以继续通过配置这些应用特征的实现,来满足它们各自的运维要求(例如:不同环境里 Ingress 实现在满足合规性和安全性上的差异)。 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | -------------------------------------------------------------------------------- /02~集群编排/资源调度/数据调度.md: -------------------------------------------------------------------------------- 1 | # 数据调度 2 | 3 | 互联网时代的一个重要特点是数据来源广泛,出于数据生产和容灾的需要,数据通常是跨地域摆放在不同地区的不同机房。大数据服务希望容纳所有数据,做到一条 SQL 可以访问到全球任何数据,对这些数据做分析、聚合等操作。但要做到这一点需要面临极大的挑战,其中最大的挑战来自网络。不同于大数据机房内部网络,广域网的网络延迟高、带宽小、价格高、稳定性差,如果每天将全球产生的数据全部集中在一个集群需要极大的网络消耗。考虑到数据量增长的速度远大于广域网发展的速度,很多的大数据计算引擎并没有建一个超大的数据中心将所有数据容纳进来,而是在全球范围根据数据产生的位置就近建设数据中心。 4 | 5 | 目前工业界成熟的大数据分析系统大都运行在一个数据中心里,跨多数据中心这个方向并没有得到很好地研究。学术界近几年有一些多数据中心调度的工作,但大多致力于 DAG 内 Task 的调度,如 Iridium 期望通过 Task 调度和数据摆放减少跨域网络对任务时效性的影响,Clarinet 提出一种 WAN-aware 的 Query Optimizer,Tetrium 在 Task 和 Job 调度时同时考虑了计算和网络资源因素。这些研究绝大部分更关注单个作业的性能影响,而没有从大数据服务提供商的角度,关注带宽成本和整体性能。 6 | 7 | # 数据调度架构 8 | 9 | 在数据调度中,我们考虑的有优化数据的摆放位置,确定采取什么迁移策略,以及当需要的数据不在本地时,如何进行数据复制和直读、写回,最终实现跨地域长传带宽的流控和整体存储成本的降低。 10 | 11 | 较为粗放的策略,即考虑数据中心的业务独立。大部分的计算只需要读取内部产生的数据,数据中心之间有少量的数据依赖,由于量比较少,所以作业跨域直接访问数据也不会产生太大流量压力。数据中心各自根据业务需求和增速进行容量规划,包括计算和存储能力的扩容等。初期各数据中心整体负载不高,因此独立规划基本能满足业务的需要。 12 | 13 | 不过随着业务的不断增长和变化,越来越多的作业需要依赖其他数据中心的数据进行计算,跨域带宽逐渐成为系统瓶颈。另一方面,随着机器规模不断增长,机房、网络等基础设施逐渐成为数据中心进一步扩大规模的障碍,各数据中心独立规划的弊端逐渐暴露,开始出现各数据中心忙闲不均、资源浪费的情况。 14 | 15 | 为了解决跨域带宽和各数据中心规划中遇到的问题,我们可以在数据中心上层增加了一层调度层,用于调度数据和计算。这层调度独立于数据中心内部的调度,主要目的在于: 16 | 17 | - 调度数据,包括数据的迁移和复制。通过迁移数据,均衡各数据中心存储负载,实现集群层面的存储计算分离,并保证不会由于访问远程数据造成带宽雪崩;通过复制(缓存)数据,避免对同一数据的频繁跨域访问,减少带宽消耗; 18 | 19 | - 调度计算,包括整体业务的迁移和 SQL 粒度的调度。通过整体业务的迁移,均衡数据中心计算负载,通过将联系紧密的业务放在一起从而减少跨域数据依赖。但业务整体迁移需要迁移大量的历史数据,会消耗大量带宽。因此我们加入了 SQL 粒度的跨机房调度,希望在不迁移或复制数据的情况下,减少带宽消耗(将计算调度到数据所在数据中心)和均衡集群负载。 20 | -------------------------------------------------------------------------------- /10~边缘计算/README.md: -------------------------------------------------------------------------------- 1 | # 边缘计算 2 | 3 | 在集中化日益提高的时候,网络的发展不一定赶的上应用的需求发展。同时网络总存在着一些不稳定性。为了解决这些场景的问题,就出来了边缘计算。边缘计算将应用程序、数据和计算能力服务从集中式数据中心推到网络的逻辑极限,靠近用户、设备和传感器。它使计算的使用方能够在正确的时间将正确的数据放在正确的位置,支持快速和安全的访问。 4 | 5 | 边缘计算典型的应用场景就是物联网,现阶段物联网的主要运算能力都是由云计算提供的,边缘计算不是在中央服务器里整理后实施处理,而是在网络内的各设备实时处理,所有的智能设备都能成为“数据中心”。因此,边缘计算带来了更快的传输和响应速度,同时边缘计算利用了传统云计算的遗漏区域,产能比更划算。 6 | 7 | ![边缘计算,节点-计算-数据中心](https://s2.ax1x.com/2019/10/03/uw7zd0.png) 8 | 9 | # 背景分析 10 | 11 | 降低成本,包括流量成本、存储成本、云端流式计算资源成本。在弱网络环境下,提升流式计算结果的准确性。 12 | 13 | ## 场景案例 14 | 15 | ### 数据分析师的取数场景 16 | 17 | 在数据分析场景下,数据分析师经常使用 10 分钟平均值或 15 分钟平均值作为样本数据进行分析,而不会直接使用原始采集数据。如果云平台当中只有原始采集数据,数据分析师通常使用以下几种方式获取 10 分钟平均值: 18 | 19 | 导出原始数据,通过数据预处理工具获取 10 分钟平均值 20 | 云平台增加流式计算任务,计算 10 分钟平均值并实时保存在数据库 21 | 通过大数据平台对历史数据执行离线任务,计算历史数据的 10 分钟平均值并保存在数据库 22 | 上述的几种方案能够让数据分析师拿到 10 分钟平均值,但是成本都很高,也不方便。 23 | 24 | 通过边缘流式计算可以很好的解决上述问题,边缘节点在边缘侧通过流式计算得到 10 分钟平均值,然后上报至云端 iothub,经由规则引擎将 10min 数据转存到数据库,可以大大降低数据分析师的取数难度。 25 | 26 | ### 运维人员的实时监控场景 27 | 28 | 在物联网场景下,设备测量数据因为各种因素(网络因素、设备自身精度因素)经常会出现抖动情况,如果对设备的实时采集值配置阈值告警,经常会出现误告警,导致用户需要处理大量无用告警,逐渐用户对告警的准确性失去信心,阈值告警形同虚设。 29 | 30 | 针对这种场景,用户可以借助流式计算的能力来降低数据抖动带来的偏差,常见的方案有: 31 | 32 | 按平均值告警:通过流式计算获取 10 分钟平均值、10 分钟最大值、10 分钟最小值、10 分钟计算样本数,然后设定阈值规则,比如“10 分钟平均值>阈值 且 10 分钟计算样本数>100” 33 | 按持续时间告警:通过流式计算找出实时采集值一直大于指定阈值且持续时间很长的设备,比如"设备温度>100℃ 且 持续时长>5 分钟" 34 | 在网络不稳定的场景下,上述两种方案如果通过云端流式计算来实现,最终得到的计算值准确度不高,比如当设备出现 5 分钟无连接的时候,云端再去计算 10 分钟平均值,计算的结果并不准确。而边缘侧是内网环境,网络异常的概率大大降低。在边缘侧将统计值计算好后再上报云端,可以大大提升流式计算统计结果的准确性。 35 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/09~Swarm/Swarm.md: -------------------------------------------------------------------------------- 1 | # Swarm 2 | 3 | Docker Swarm 是 Docker 自己的 Docker 容器本地集群解决方案,具有与 Docker 生态系统紧密集成并使用自己的 API 的优势。它监视跨服务器群集的容器数量,是在没有其他硬件的情况下创建群集 docker 应用程序的最便捷方式。它为 Dockerized 应用程序提供了一个小规模但有用的编排系统。 4 | 5 | # 概述 6 | 7 | ## 优缺点 8 | 9 | 使用 Docker Swarm 的优点: 10 | 11 | - **以更快的速度运行:**当您使用虚拟环境时,您可能已经意识到它需要很长时间,并且包括启动和启动您要运行的应用程序的繁琐程序。使用 Docker Swarm,这不再是一个问题。Docker Swarm 消除了启动完整虚拟机的需要,使应用程序能够快速在虚拟和软件定义的环境中运行,并有助于 DevOps 实施。 12 | - **文档提供了所有信息:** Docker 团队在文档方面脱颖而出!Docker 正在迅速发展,并为整个平台赢得了热烈的掌声。当版本在很短的时间间隔内发布时,某些平台不会维护文档。但是 Docker Swarm 从未与它妥协。如果该信息仅适用于 Docker Swarm 的某些版本,则文档会确保更新所有信息。 13 | - **提供简单快速的配置:** Docker Swarm 的一个主要优点是它简化了问题。Docker Swarm 使用户可以自己配置,将其放入代码中并轻松部署。由于 Docker Swarm 可以在各种环境中使用,因此需求不受应用程序环境的约束。 14 | - **确保应用程序是孤立的**:Docker Swarm 注意每个容器与其他容器隔离并拥有自己的资源。可以部署各种容器以在不同堆栈中运行单独的应用程序。除此之外,当每个应用程序在自己的容器上运行时,Docker Swarm 会清除应用程序删除。如果不再需要该应用程序,则可以删除其容器。它不会在您的主机操作系统上留下任何临时或配置文件。 15 | - **版本控制和组件重用**:使用 Docker Swarm,您可以跟踪容器的连续版本,检查差异或回滚到先前版本。容器重复使用前面层中的组件,这使得它们显着轻量级。 16 | 17 | 使用 Docker Swarm 的缺点: 18 | 19 | - **Docker 依赖于平台**:Docker Swarm 是一个 Linux 激动人心的平台。虽然 Docker 支持 Windows 和 Mac OS X,但它利用虚拟机在非 Linux 平台上运行。设计为在 Windows 上的 Docker 容器中运行的应用程序无法在 Linux 上运行,反之亦然。 20 | - **不提供存储选项**:Docker Swarm 不提供将容器连接到存储的无障碍方式,这是主要缺点之一。其数据量需要在主机和手动配置上进行大量即兴创作。如果您期望 Docker Swarm 解决存储问题,可能会以高效且用户友好的方式完成。 21 | - **监控不良**:Docker Swarm 提供有关容器的基本信息,如果您正在寻找基本的监控解决方案,那么 Stats 命令就足够了。如果您正在寻找高级监控,那么 Docker Swarm 永远不是一个选择。虽然有像 CAdvisor 这样的第三方工具可以提供更多监控,但使用 Docker 本身实时收集有关容器的更多数据是不可行的。 22 | -------------------------------------------------------------------------------- /01~虚拟化/Xen/虚拟机创建.md: -------------------------------------------------------------------------------- 1 | # Create: 虚拟机创建 2 | 3 | ## ISO Storage 4 | 5 | What you can do is issue the following command: 6 | 7 | ```sh 8 | xe sr-create name-label= type=iso device-config:legacy_mode=true device-config:location= content-type=iso 9 | ``` 10 | 11 | And with generic fields populated: 12 | 13 | xe sr-create name-label=Local type=iso device-config:legacy_mode=true device-config:location=/vm/iso content-type=iso 14 | 15 | When you run that command, if successful, it will return a UUID for the created storage repository. Please note, you can repeat the same command as many times as you want, and each time it will create a new storage repository, which will show in your GUI afterwards as a separate entry. 16 | 17 | xe sr-create name-label=Local type=iso device-config:legacy_mode=true device-config:location=/vm/iso content-type=iso 3476e496-185f-9eba-0f89-bb822db31ebd You can do this from the local shell after connecting via SSH: 18 | 19 | ![Local shell](http://www.dedoimedo.com/images/computers_years/2012_1/xenserver-ssh-local-shell.png) 20 | 21 | ![SR added](http://www.dedoimedo.com/images/computers_years/2012_1/xenserver-local-added.png) 22 | 23 | And then, when you try to install the VM, you will find Local listed. Notice the two identical entries, which will show up if you enter the same command twice, so do note this as this could confuse you. Not sure if this is a bug, but this is how it works. 24 | 25 | ![Local SR shows twice](http://www.dedoimedo.com/images/computers_years/2012_1/xenserver-local-twice.jpg) 26 | -------------------------------------------------------------------------------- /01~虚拟化/Ceph/README.md: -------------------------------------------------------------------------------- 1 | # Ceph 2 | 3 | Ceph 是 linux 系统中一个分布式文件系统,能够在维护 POSIX 兼容性的同时加入了复制和容错功能,由 Sage Weil 在 University of California, SantaCruz(UCSC)实施。Ceph 内部实现了分布式数据对象存储,对外可以提供文件系统、对象、块设备的访问方式,实现了统一存储平台。Ceph 社区最新版本是 14,而 Ceph 12 是市面用的最广的稳定版本。 4 | 5 | 目前,Ceph 主要有三种企业级应用场景: 6 | 7 | - IOPS 密集型:这种类型的场景通常是支撑在虚拟化/私有云上运行数据库。如在 OpenStack 上运行 Mysql、MariaDB 或 PostgreSQL 等。IOPS 密集型场景对磁盘的性能要求较高,最好使用全闪架构。如果使用混合架构,机械盘转速需要 1.2 万,并使用高速盘存储频繁写操作的日志或元数据。 8 | - 高吞吐量型:这种类型的应用场景主要是大块数据传输,如图像、视频、音频文件等。高吞吐量型磁盘的要求没有 IOPS 密集型高,但需要配置较高的网络。同时也需要配置 SSD 来处理写日志。 9 | - 高容量型:这种场景主要用于存储归档、离线数据。它对磁盘的容量要求高,对性能无过多要求。写日志也可以存储在 HDD 上。 10 | 11 | # Ceph 架构 12 | 13 | Ceph 由储存管理器(Object storage cluster 对象存储集群,即:Osd 守护进程)、,集群监视器(Ceph Monitor)和元数据服务器(Metadata server cluster, mds)构成。其中,元数据服务器 MDS 仅仅在客户端(数据用户 Client)通过文件系统方式使用 Ceph 时有用。当客户端通过块设备或对象存储使用 CEPH 时,可以没有 MDS。一个 Ceph 储存集群,由一系列的节点(具备 CPU 和 MEM 的计算机)、储存设备和传输网络构成。 14 | 15 | ![Ceph 架构](https://s2.ax1x.com/2020/01/14/lqE5LQ.png) 16 | 17 | Ceph 存储集群由三类守护进程组成:OSD,Monitor 和 Manager。 18 | 19 | - OSD:OSD 是 Ceph 存储数据的空间,通常一个 HDD 是一个 OSD,并且不建议做 RAID(独立硬盘冗余阵列)。每个 OSD 有一个 OSD 守护进程。Ceph OSD 利用 Ceph 节点的 CPU、内存和网络资源来执行数据复制、纠删码、数据恢复、监控和报告功能。 20 | 21 | - Monitor:Monitor 负责维护 Ceph 存储集群,主要是集群中数据的主副本以及存储集群的当前状态。注意,多个 Monitor 的信息需要强一致性,因此要求 Monitor 节点之间的系统时间是一致的,并且网络延时要低。 22 | 23 | - Manager:Manager 是 Ceph 12.8 中的新功能,它维护放置组(PG)、进程元数据和主机元数据的详细信息。这部分功能此前由 Monitor 完成(其目的是提高 Ceph 集群的性能)。Manager 可以处理只读 Ceph CLI 查询请求,例如放置组统计信息等。此外,Manager 还提供 RESTful 监控 API。 24 | 25 | 如果要使用 Ceph 文件系统和对象接口,Ceph 集群还需要如下节点: 26 | 27 | - 元数据服务器(Metadata Server,简称 MDS):每个 MDS 节点运行 MDS 守护程序(Ceph-mds)管理与 Ceph 文件系统(CephFS)上存储的文件相关的元数据。 28 | - 对象网关:Ceph 对象网关节点上运行 Ceph RADOS 网关守护程序(Ceph-radosgw)。它是一个构建在 librados 之上的对象存储接口,也是一个为应用程序提供 Ceph 存储集群的 RESTful 网关。Ceph 对象网关支持两个接口:S3 和 OpenStack Swift。 29 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/04~网络/网络模型.md: -------------------------------------------------------------------------------- 1 | # 网络 2 | 3 | 要想实现网络通信,机器至少需要一个网络接口(物理接口或虚拟接口)与外界相通,并可以收发数据包;另外,如果不同子网之间要进行通信,则需要额外的路由机制。Docker 的网络接口默认都是虚拟接口。虚拟接口的最大优势就是转发效率极高!之所以会这样,那是因为 Linux 通过在内核中进行数据复制来实现虚拟接口间的数据转发,即直接复制发送接口的发送缓存中的数据包到接收接口的接收缓存中,而无需通过外部物理网络设备进行交换。对于本地系统和容器内系统来看,虚拟接口和一个正常的以太网卡相比并无区别,只是虚拟接口的速度要快得多。 4 | 5 | 创建一对虚拟接口,分别放到宿主机和容器的命名空间中;宿主机一端的虚拟接口连接到默认的 docker0 网桥或指定网桥上,并具有一个以 veth 开头的唯一的名字;容器一端的虚拟接口将被放到容器中,并修改名称为 eth0,且这个接口只对该容器的命名空间可见;4. 从网桥可用地址段中获取一个空闲的地址分配给容器的 eth0(如 172.17.0.2/16),并配置默认路由网关为 docker0 网卡的内部接口 docker0 的 IP 地址(如 172.17.42.1/16);完成以上这些,容器就可以使用自身可见的 eth0 虚拟网卡来连接其他容器和访问外部网络。另外,可以在容器创建启动时通过--net 参数来指定容器的网络配置 6 | 7 | # CNM 网络模型 8 | 9 | Docker 通过 libnetwork 实现了 CNM 网络模型。libnetwork 设计 doc 中对 CNM 模型的简单诠释如下: 10 | 11 | ![image](https://user-images.githubusercontent.com/5803001/45594781-e6211a80-b9d2-11e8-8252-3d4f52277a17.png) 12 | 13 | CNM 模型有三个组件: 14 | 15 | Sandbox(沙盒):每个沙盒包含一个容器网络栈(network stack)的配置,配置包括:容器的网口、路由表和 DNS 设置等。 16 | Endpoint(端点):通过 Endpoint,沙盒可以被加入到一个 Network 里。 17 | Network(网络):一组能相互直接通信的 Endpoints。 18 | 19 | CNM 模型在 Linux 上的参考实现技术,比如:沙盒的实现可以是一个 Linux Network Namespace;Endpoint 可以是一对 VETH;Network 则可以用 Linux Bridge 或 Vxlan 实 20 | 21 | veth 对只是不同网络命名空间通信的一种解决方案,还有其他方案。 22 | 23 | Linux Bridge,即 Linux 网桥设备,是 Linux 提供的一种虚拟网络设备之一。其工作方式非常类似于物理的网络交换机设备。Linux Bridge 可以工作在二 24 | 25 | 层,也可以工作在三层,默认工作在二层。工作在二层时,可以在同一网络的不同主机间转发以太网报文;一旦你给一个 Linux Bridge 分配了 IP 地址, 26 | 27 | 也就开启了该 Bridge 的三层工作模式。在 Linux 下,你可以用 iproute2 工具包或 brctl 命令对 Linux bridge 进行管理。 28 | 29 | VETH(Virtual Ethernet )是 Linux 提供的另外一种特殊的网络设备,中文称为虚拟网卡接口。它总是成对出现,要创建就创建一个 pair。一个 Pair 中的 30 | 31 | veth 就像一个网络线缆的两个端点,数据从一个端点进入,必然从另外一个端点流出。每个 veth 都可以被赋予 IP 地址,并参与三层网络路由过程。Network namespace,网络名字空间,允许你在 Linux 创建相互隔离的网络视图,每个网络名字空间都有独立的网络配置,比如:网络设备、路由表 32 | 33 | 等。新建的网络名字空间与主机默认网络名字空间之间是隔离的。我们平时默认操作的是主机的默认网络名字空间。 34 | 35 | ![image](https://user-images.githubusercontent.com/5803001/45594763-b5d97c00-b9d2-11e8-9001-377d8957d488.png) 36 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/01~配置部署/安装与配置.md: -------------------------------------------------------------------------------- 1 | # Docker 安装与配置 2 | 3 | ## 安装 4 | 5 | ### DaoCloud 6 | 7 | - 极速安装 8 | 9 | ```sh 10 | curl -sSL https://get.daocloud.io/docker | sh 11 | ``` 12 | 13 | - 配置加速器 14 | 15 | ``` 16 | curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://a2922952.m.daocloud.io 17 | ``` 18 | 19 | 如果到这里安装完毕,则直接进入下一步,否则使用官网安装方法。 20 | 21 | ### 官网安装 22 | 23 | - 配置仓库 24 | 25 | ``` 26 | sudo apt-get -y install \ 27 | apt-transport-https \ 28 | ca-certificates \ 29 | curl 30 | 31 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 32 | 33 | sudo add-apt-repository \ 34 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 35 | $(lsb_release -cs) \ 36 | stable" 37 | 38 | sudo apt-get update 39 | ``` 40 | 41 | - 获取 Docker CE 42 | 43 | ``` 44 | sudo apt-get -y install docker-ce 45 | ``` 46 | 47 | - 测试安装是否成功 48 | 49 | ``` 50 | sudo docker run hello-world 51 | ``` 52 | 53 | ## 修改存储路径 54 | 55 | ``` 56 | # 关闭 docker 57 | service docker stop 58 | 59 | # 编译配置文件 60 | vim /etc/default/docker 61 | 62 | # 在配置文件中添加如下行 63 | DOCKER_OPTS="--insecure-registry 10.196.108.176:5000 --dns 114.114.114.114 --dns 8.8.8.8 --dns 8.8.4.4 -g /mnt -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock" 64 | 65 | # 重新启动 docker 66 | service docker start 67 | ``` 68 | 69 | ## 配置管理 70 | 71 | ```sh 72 | # 在主节点启动 Swarm 73 | docker swarm init 74 | 75 | # 查看 Swarm 密钥 76 | docker swarm join-token -q worker 77 | 78 | # 在主节点启动 Procontainer 79 | docker run -it -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer 80 | 81 | # 在主节点启动 Registry 82 | docker run -d -p 5000:5000 --restart=always --name registry registry:2 83 | 84 | # 将子节点加入到 Swarm 85 | docker swarm join \ 86 | --token ${TOKEN} \ 87 | 10.196.108.176:2377 88 | ``` 89 | 90 | # Docker Compose 91 | 92 | ```sh 93 | # 下载执行文件 94 | $ sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 95 | 96 | # 赋权 97 | $ sudo chmod +x /usr/local/bin/docker-compose 98 | ``` 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg) ![](https://parg.co/bDm) 2 | 3 | ![封面图](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417205812.png) 4 | 5 | # Cloud Series 6 | 7 | # Preface(前言) 8 | 9 | ![Cloud Native Computing Foundation](https://s1.ax1x.com/2020/09/07/wKC9Tx.md.png) 10 | 11 | ## 虚拟化 12 | 13 | 在计算机科学中,虚拟化技术(Virtualization)是一种资源管理(优化)技术,将计算机的各种物理资源(e.g. CPU、内存以及磁盘空间、网络适配器等 IO 设备)予以抽象、转换,然后呈现出来的一个可供分割并任意组合为一个或多个(虚拟)计算机的配置环境。虚拟化技术打破了计算机内部实体结构间不可切割的障碍,使用户能够以比原本更好的配置方式来应用这些计算机硬件资源。而这些资源的虚拟形式将不受现有架设方式,地域或物理配置所限制。虚拟化技术是一个广义的术语,根据不同的对象类型可以细分为: 14 | 15 | - 平台虚拟化(Platform Virtualization):针对计算机和操作系统的虚拟化。 16 | 17 | - 资源虚拟化(Resource Virtualization):针对特定的系统资源的虚拟化,如内存、存储、网络资源等。 18 | 19 | - 应用程序虚拟化(Application Virtualization):包括仿真、模拟、解释技术等,如 Java 虚拟机(JVM)。 20 | 21 | ![虚拟化技术发展](https://s2.ax1x.com/2019/08/31/mx0hXn.jpg) 22 | 23 | 从虚拟化技术诞生以来,IaaS / PaaS / SaaS 概念陆续被提了出来,各种容器技术层出不穷。 24 | 25 | ## Kubernetes 与编排 26 | 27 | 随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多地提及。IaaS、PaaS 和 SaaS 是云计算的三种基本服务类型,分别表示关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务,以及关注业务应用的软件即服务。容器的出现,使原有的基于虚拟机的云主机应用,彻底转变为更加灵活和轻量的容器与编排调度的云平台应用。 28 | 29 | ![Kubernetes Master](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211132.png) 30 | 31 | 然而容器单元越来越散落使得管理成本逐渐上升,大家对容器编排工具的需求前所未有的强烈,Kubernetes、Mesos、Swarm 等为云原生应用提供了强有力的编排和调度能力,它们是云平台上的分布式操作系统。容器编排是通常可以部署多个容器以通过自动化实现应用程序的过程。像 Kubernetes 和 Docker Swarm 这样的容器管理和容器编排引擎,使用户能够指导容器部署并自动执行更新,运行状况监视和故障转移过程。 32 | 33 | Kubernetes 是目前世界范围内关注度最高的开源项目,它是一个出色的容器编排系统,用于提供一站式服务。Kubernetes 出身于互联网行业巨头 Google,它借鉴了由上百位工程师花费十多年时间打造的 Borg 系统的理念,安装极其简易,网络层对接方式十分灵活。Kubernetes 和 Mesos 的出色表现给行业中各类工程师的工作模式带来了颠覆性的改变。他们再也不用关注每一台服务器,当服务器出现问题时,只要将其换掉即可。业务开发工程师不必再过分关注非功能需求,只需专注自己的业务领域即可。而中间件开发工程师则需要开发出健壮的云原生中间件,用来连接业务应用与云平台。 34 | 35 | # About 36 | 37 | ## Copyright & More | 延伸阅读 38 | 39 | 您还可以前往 [NGTE Books](https://ng-tech.icu/books-gallery/) 主页浏览包含知识体系、编程语言、软件工程、模式与架构、Web 与大前端、服务端开发实践与工程架构、分布式基础架构、人工智能与深度学习、产品运营与创业等多类目的书籍列表: 40 | 41 | [![NGTE Books](https://s2.ax1x.com/2020/01/18/19uXtI.png)](https://ng-tech.icu/books-gallery/) 42 | 43 | ## Links 44 | 45 | - [2018~kubernetes-tutorial #Series#](https://github.com/KeKe-Li/kubernetes-tutorial): Running Kubernetes cluster Locally tutorial. 46 | -------------------------------------------------------------------------------- /01~虚拟化/README.md: -------------------------------------------------------------------------------- 1 | # 虚拟化与云计算 2 | 3 | 在计算机科学中,虚拟化技术(Virtualization)是一种资源管理(优化)技术,将计算机的各种物理资源(e.g. CPU、内存以及磁盘空间、网络适配器等 IO 设备)予以抽象、转换,然后呈现出来的一个可供分割并任意组合为一个或多个(虚拟)计算机的配置环境。虚拟化技术打破了计算机内部实体结构间不可切割的障碍,使用户能够以比原本更好的配置方式来应用这些计算机硬件资源。而这些资源的虚拟形式将不受现有架设方式,地域或物理配置所限制。虚拟化技术是一个广义的术语,根据不同的对象类型可以细分为: 4 | 5 | - 平台虚拟化(Platform Virtualization):针对计算机和操作系统的虚拟化。 6 | 7 | - 资源虚拟化(Resource Virtualization):针对特定的系统资源的虚拟化,如内存、存储、网络资源等。 8 | 9 | - 应用程序虚拟化(Application Virtualization):包括仿真、模拟、解释技术等,如 Java 虚拟机(JVM)。 10 | 11 | ![虚拟化技术发展](https://s2.ax1x.com/2019/08/31/mx0hXn.jpg) 12 | 13 | 如 [DevOps 与 SRE 实战](https://ng-tech.icu/books/Backend-Notes/#/devops) 中所述,容器解除了开发和运维之间的隔阂,但同时也带来了一些挑战,比如频繁的发布变更如何控制,如何控制容器集群的行为,如何拆分应用到容器之中等。这是一个专门用于容器编排调度的工具呼之欲出,Kubernetes 的出现彻底改变了局面,可以说它直接改变了应用的基础架构。 14 | 15 | ![应用基础架构变迁](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211422.png) 16 | 17 | # 虚拟机 18 | 19 | 虚拟机由某些特定的硬件和内核虚拟化组成,运行客户操作系统。称为管理程序的软件创建虚拟化硬件,其可以包括虚拟磁盘,虚拟网络接口,虚拟 CPU 等。虚拟机还包括可以与此虚拟硬件通信的宾客内核。管理程序可以托管,这意味着它是一些在主机操作系统(MacOS)上运行的软件,如示例中所示。它也可以是裸机,直接在机器硬件上运行(替换你的操作系统)。无论哪种方式,管理程序方法都被认为是重量级的,因为它需要虚拟化多个部分(如果不是全部硬件和内核)。 20 | 21 | ![虚拟机与容器间的比较](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211553.png) 22 | 23 | VM 需要硬件虚拟化才能实现机器级隔离,而容器则只需要在同一操作系统内进行隔离操作。随着隔离空间数量的增加,开销差异变得非常明显。 24 | 25 | # 容器 26 | 27 | 在过去几年里,云平台发展迅速,但其中困扰运维工程师最多的,是需要为各种迥异的开发语言安装相应的运行时环境。虽然自动化运维工具可以降低环境搭建的复杂度,但仍然不能从根本上解决环境的问题。 28 | 29 | ![Containerized Applications](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211633.png) 30 | 31 | Docker 的出现成为了软件开发行业新的分水岭,容器技术的成熟也标志着技术新纪元的开启。Docker 提供了让开发工程师可以将应用和依赖封装到一个可移植的容器中的能力,这项举措使得 Docker 大有席卷整个软件行业并且进而改变行业游戏规则的趋势,这像极了当年智能手机刚出现时的场景——改变了整个手机行业的游戏规则。Docker 通过集装箱式的封装方式,让开发工程师和运维工程师都能够以 Docker 所提供的镜像分发的标准化方式发布应用,使得异构语言不再是捆绑团队的枷锁。 32 | 33 | ![Traditional Deployment -> Virtualized Deployment -> Container Deployment](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211652.png) 34 | 35 | 容器是包含应用程序代码,配置和依赖关系的软件包,可提供运营效率和生产力。容器为我们提供了可预测的,可重复的和不可变的运行预期,容器的兴起是 DevOps 即服务的一个巨大推动因素,可以克服当今面临的最大安全障碍。容器化通过在操作系统级别进行虚拟化来使应用程序可移植,从而创建基于内核的隔离的封装系统。容器化的应用程序可以放在任何地方,无需依赖项运行或需要整个 VM,从而消除了依赖关系。 36 | 37 | 作为独立的单元,容器能够在任何主机操作系统,CentOS,Ubuntu,MacOS,甚至是像 Windows 这样的非 UNIX 系统中运行。容器还充当标准化的工作或计算单元。一个常见的范例是每个容器运行单个 Web 服务器,数据库的单个分片或单个 Spark 工作程序等,只需要扩展容器的数量就能够便捷地扩展应用。每个容器都有一个固定的资源配置(CPU,RAM,线程数等),并且扩展应用程序需要只扩展容器的数量而不是单个资源原语。当应用程序需要按比例放大或缩小时,这为工程师提供了更容易的抽象。容器也是实现微服务架构的一个很好的工具,每个微服务只是一组协作容器。例如,可以使用单个主容器和多个从容器来实现 Redis 微服务。 38 | -------------------------------------------------------------------------------- /02~集群编排/OAM/README.md: -------------------------------------------------------------------------------- 1 | # OAM 2 | 3 | OAM 的愿景是以标准化的方式沟通和连接应用开发者、运维人员、应用基础设施,让云原生应用管理与交付变得更加简洁,高效,并且可控。OAM 为什么值得关注? 4 | 5 | - 关注点分离:开发者关注应用本身,运维人员关注模块化运维能力,让应用管理变得更轻松、应用交付变得更可控; 6 | - 平台无关与高可扩展:应用定义与平台层实现解耦,应用描述支持任意扩展和跨环境实现; 7 | - 模块化应用运维特征:可以自由组合和支持模块化实现的运维特征描述。 8 | 9 | Kubernetes 项目作为容器编排领域的事实标准,成功推动了诸如阿里云 Kubernetes(ACK)等云原生服务的迅速增长。但同时我们也关注到,Kubernetes 的核心 API 资源比如 Service、Deployment 等,实际上只是应用中的不同组成部分,并不能代表一个应用的全部。也许我们可以通过像 Helm charts 这样的方式来尝试表达一个可部署的应用,可一旦部署起来,实际运行的应用中却依旧缺乏以应用为中心的约束模型。 10 | 11 | 这些问题都反映出,Kubernetes 以及云原生技术栈需要一种以应用为中心的 API 资源来提供一个专注于应用管理的、标准的、高度一致的模型,这个 API 资源可以代表完整运行的应用本身,而不仅仅是应用模板或者一个应用的几个组成部分,这就是今天阿里云与微软联合宣布推出开放应用模型 Open Application Model(OAM)的原因。 12 | 13 | # 背景特性 14 | 15 | 与 PaaS 应用模型相比,OAM 有很多独有的特点,其中最重要一点是:平台无关性。虽然我们目前发布的 OAM 实现(rudr)是基于 Kubernetes 的,但 Open Application Model 与 Kubernetes 并没有强耦合。实际上,OAM 可以实现到任意平台或运行环境之上,这当然也包括边缘计算与物联网的场景。我们也认同 Kubernetes 在很多运行环境中可能并不是最好的选择,或者是像 Serverless 这类用户并不需要关心基础设施复杂性的运行环境。在这些场景下,OAM 都可以提供完全一致的应用管理体验。 16 | 17 | 第二个重要的特点是,OAM 的 specification(OAM 规范)在设计上天然是可扩展的。OAM 不像 PaaS 那样自成封闭体系,也不会通过某种独有的应用管理环境来屏蔽掉底层平台的特点(比如:在 Kubernetes 之上”盖一个大帽子“)。相反,OAM 使平台层可以通过应用特征系统(Trait system)来体现平台的特性和差异性。也就是说,只要不同的平台都能够提供应用所需要的某些应用特征 (Trait),开发人员就能轻松地研发跨平台的应用。类似地,哪怕最底层的硬件提供商,也可以通过应用特征系统来体现其平台特性。 18 | 19 | OAM 的整体设计,就是为了避免在平台可移植性中经常发生的“最小公分母”锁定问题。相反,OAM 不但提供了可移植性的能力,它还确保了每个平台有能力去透出独有的特性和用途。OAM 让开发人员可以自由地针对不同平台以标准方式在可移植性和差异化功能之间取得平衡。 20 | 21 | ## 关注点分离 22 | 23 | OAM 是一个专注于描述应用的标准规范。有了这个规范,应用描述就可以彻底与基础设施部署和管理应用的细节分开。这种关注点分离(Seperation of Conerns)的设计好处是非常明显的。 24 | 25 | 举个例子,在实际生产环境中,无论是 Ingress,CNI,还是 Service Mesh,这些表面看起来一致的运维概念,在不同的 Kubernetes 集群中可谓千差万别。通过将应用定义与集群的运维能力分离,我们就可以让应用开发者更专注于应用本身的价值点,而不是”应用部署在哪“这样的运维细节。 26 | 27 | 此外,关注点的分离让平台架构师可以轻松地把平台的运维能力封装成可被复用的组件,从而让应用开发者能够专注于将这些运维组件与代码进行集成,从而快速、轻松地构建可信赖的应用。Open Application Model 的目标是让简单的应用管理变得更加轻松,让复杂的应用交付变得更加可控。 28 | 29 | ## 平台无关、高可扩展的应用描述能力 30 | 31 | 与 PaaS 应用模型相比,OAM 有很多独有的特点,其中最重要一点是:平台无关性。虽然我们目前发布的 OAM 实现(rudr)是基于 Kubernetes 的,但 Open Application Model 与 Kubernetes 并没有强耦合。实际上,OAM 可以实现到任意平台或运行环境之上,这当然也包括边缘计算与物联网的场景。我们也认同 Kubernetes 在很多运行环境中可能并不是最好的选择,或者是像 Serverless 这类用户并不需要关心基础设施复杂性的运行环境。在这些场景下,OAM 都可以提供完全一致的应用管理体验。 32 | 33 | 第二个重要的特点是,OAM 的 specification(OAM 规范)在设计上天然是可扩展的。OAM 不像 PaaS 那样自成封闭体系,也不会通过某种独有的应用管理环境来屏蔽掉底层平台的特点(比如:在 Kubernetes 之上”盖一个大帽子“)。相反,OAM 使平台层可以通过应用特征系统(Trait system)来体现平台的特性和差异性。也就是说,只要不同的平台都能够提供应用所需要的某些应用特征 (Trait),开发人员就能轻松地研发跨平台的应用。类似地,哪怕最底层的硬件提供商,也可以通过应用特征系统来体现其平台特性。 34 | 35 | OAM 的整体设计,就是为了避免在平台可移植性中经常发生的“最小公分母”锁定问题。相反,OAM 不但提供了可移植性的能力,它还确保了每个平台有能力去透出独有的特性和用途。OAM 让开发人员可以自由地针对不同平台以标准方式在可移植性和差异化功能之间取得平衡。 36 | -------------------------------------------------------------------------------- /02~集群编排/云计算.md: -------------------------------------------------------------------------------- 1 | # 云计算 2 | 3 | 农耕时代、工业时代与信息时代可谓三个明显的分水岭,每个时代都会出现很多新兴的领域,而在信息时代,软件在不断地改变着世界。随着开源文化越来越被认可,以及社区文化越来越成熟,使用优秀的开源产品作为基础构架来快速搭建系统以实现市场战略,成了当今最优的资源配比方案。 4 | 5 | 仅通过开源产品搭建并运维一个高可用、高度弹性化的平台,进而实现互联网近乎 100%的可用性,难度可想而知。因此,在提供技术思路的同时,进一步提供整套云解决方案以保障不断扩展的非功能需求,便成了当今新一代互联网平台的追求。在信息技术的大潮中,每一次通信模式的升级都会给这个世界的合作模式带来变革。 6 | 7 | 随着互联网在 21 世纪初被大规模接入,互联网由基于流量点击赢利的单方面信息发布的 Web 1.0 业务模式,转变为由用户主导而生成内容的 Web2.0 业务模式。因此,互联网应用系统所需处理的访问量和数据量均疾速增长,后端技术架构也因此面临着巨大的挑战。Web 2.0 阶段的互联网后端架构大多经历了由 All inOne 的单体式应用架构渐渐转为更加灵活的分布式应用架构的过程,而企业级架构由于功能复杂且并未出现明显的系统瓶颈,因此并未跟进。后端开发不再局限于单一技术栈,而是越来越明显地被划分为企业级开发和互联网开发。企业级开发和互联网开发的差别不仅在于技术栈差异,也在于工作模式不同,对质量的追求和对效率的提升成了两个阵营的分水岭,互联网架构追求更高的质量和效率。 8 | 9 | 随着智能手机的出现以及 4G 标准的普及,互联网应用由 PC 端迅速转向更加自由的移动端。移动设备由于携带方便且便于定位,因此在出行、网络购物、支付等方面彻底改变了现代人的生活方式。在技术方面,为了应对更加庞大的集群规模,单纯的分布式系统已经难于驾驭,因此技术圈开启了一个概念爆发的时代——SOA、DevOps、容器、CI/CD、微服务、Service Mesh 等概念层出不穷,而 Docker、Kubernetes、Mesos、SpringCloud、gRPC、Istio 等一系列产品的出现,标志着云时代已真正到来。 10 | 11 | ![mindmap](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424144450.png) 12 | 13 | 2005 年亚马逊发布了 AWS,算是拉开了云计算的序幕。云计算最重要的技术是分布式计算和分布式存储,分布式计算方面,最开始的技术是虚拟化,也就是所谓的“Software defined xxx”,通过对计算 / 存储和网络资源的虚拟化,同时能够给用户任意分配资源。但这里面一开始做的最好的只有文件存储这一块,AWS S3 及类似的对象存储产品给人们带来了云时代的一些实际的体验,但云服务器则还是走回了卖服务器的老路。 14 | 15 | 在云计算的发展过程中,有一个分支是 PaaS,最早是 2007 年推出的 Heroku,从形态上来说,它是一个 App Engine,提供应用的运行环境。2014 年 AWS 推出 Lambda 服务,Serverless 开始成为热词,从理论上说,Serverless 可以做到 NoOps、自动扩容和按使用付费,也被视为云计算的未来。但是,Serverless 本身有一些问题,比如难以解决的冷启动性能问题。 16 | 17 | - 虚拟化:将硬件资源虚拟为软件资源,然后进行统一调度和管理。 18 | - 隔离:从虚拟机到容器,再到虚拟机与容器融合,隔离的技术定义了云的形态。 19 | - 解耦:无论是后端的微服务、前端的前后端分离、组件化等等,都是将关注点分离,解耦合的过程。 20 | - 编排:大量不同的服务、任务,让他们组成一个整体,相互间能良好的协作。 21 | - 智能化:让服务个性化,或者让自动化替代以前需要人工完成的事情。 22 | - 实时化:计算和处理在极短时间内完成,从而实时的给予反馈。 23 | 24 | # 云计算 25 | 26 | 云计算作为一个虚拟化的计算机资源池,它可以托管多种不同的工作负载,包括成批的后端作业和面向用户的交互式应用程序;通过快速提供虚拟机器或物理机器,迅速部署和增加工作负载;支持冗余、自我恢复且具有高可扩展性的编程模型,以使工作负载能够从多种不可避免的硬件/软件故障中进行恢复;实时监控资源使用情况,在需要时重新平衡资源分配。 27 | 28 | 按照 NIST(National Institute of Standards and Technology,美国国家标准与技术研究院)的定义的云的标准,云计算并不是在讨论某种特定的云的技术,或者某个厂商提供的产品服务。云的定义更接近于把计算、服务等各种资源变成按需提供的服务,同时提供广泛的网络访问,以及可共享、可度量的后台资源。云更多的是一种服务,虚拟化和容器化只是云底层的技术设施,而本身并没有把它变成一种服务来提供。 29 | 30 | - 硬件系统资源的自动化调度(CloudOS); 31 | - 应用的并行化和分布化实现业务的自动扩展; 32 | - 基于初始配置的自动化业务开通和业务部署; 33 | - 基于前述能力,实现基于 SLA、QoS 等策略的自动化质量保障、故障隔离和故障自愈; 34 | - 基于大数据智能驱动的系统自动化和自动调优。 35 | 36 | 云计算本身也在进化,第一代云计算是机房托管;第二代云计算是虚拟化;第三代云计算就是云原生。随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多地提及。IaaS、PaaS 和 SaaS 是云计算的三种基本服务类型,分别表示关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务,以及关注业务应用的软件即服务。 37 | 38 | 大数据与云计算是相辅相成的,大数据的本质不一定是数据量大,更多的优势在于其数据维度多,我们能够从不同的维度入手推断出不同的结果。 39 | -------------------------------------------------------------------------------- /02~集群编排/Mesos/部署与配置.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | ## Mesos Installation & Configuration 4 | 5 | - 下载 Mesos 6 | 7 | 可以选择下载发布版本: 8 | 9 | ``` 10 | $ wget http://www.apache.org/dist/mesos/0.25.0/mesos-0.25.0.tar.gz 11 | $ tar -zxf mesos-0.25.0.tar.gz 12 | ``` 13 | 14 | 也可以选择下载 Git 的源代码版本 15 | 16 | ``` 17 | $ git clone https://git-wip-us.apache.org/repos/asf/mesos.git 18 | ``` 19 | 20 | - 系统必备 21 | 22 | 笔者使用 OSX 作为开发环境,直接安装如下必备项目: 23 | 24 | ``` 25 | # Install Command Line Tools. 26 | $ xcode-select --install 27 | 28 | # Install Homebrew. 29 | $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 30 | 31 | # Install libraries. 32 | $ brew install autoconf automake libtool subversion maven 33 | ``` 34 | 35 | - 编译 36 | 37 | ``` 38 | # Change working directory. 39 | $ cd mesos 40 | 41 | # Bootstrap (Only required if building from git repository). 42 | $ ./bootstrap 43 | 44 | # Configure and build. 45 | $ mkdir build 46 | $ cd build 47 | $ ../configure 48 | $ make 49 | ``` 50 | 51 | 如果为了提高编译的速度,可以添加如下参数`-j V=0` to `make`. 52 | 53 | ``` 54 | # Run test suite. 55 | $ make check 56 | 57 | # Install (Optional). 58 | $ make install 59 | ``` 60 | 61 | - 简单运行 62 | 63 | Mesos comes bundled with example frameworks written in C++, Java and Python. 64 | 65 | ``` 66 | # Change into build directory. 67 | $ cd build 68 | 69 | # Start mesos master (Ensure work directory exists and has proper permissions). 70 | $ ./bin/mesos-master.sh --ip=127.0.0.1 --work_dir=/var/lib/mesos 71 | 72 | # Start mesos slave. 73 | $ ./bin/mesos-slave.sh --master=127.0.0.1:5050 74 | 75 | # Visit the mesos web page. 76 | $ http://127.0.0.1:5050 77 | 78 | # Run C++ framework (Exits after successfully running some tasks.). 79 | $ ./src/test-framework --master=127.0.0.1:5050 80 | 81 | # Run Java framework (Exits after successfully running some tasks.). 82 | $ ./src/examples/java/test-framework 127.0.0.1:5050 83 | 84 | # Run Python framework (Exits after successfully running some tasks.). 85 | $ ./src/examples/python/test-framework 127.0.0.1:5050 86 | ``` 87 | 88 | ## Docker 89 | 90 | ### Built in Docker 91 | 92 | 这里说的 Mesos 与 Docker 的结合并不是指像 93 | 94 | [mesosphere-docker](https://github.com/sekka1/mesosphere-docker) 95 | 96 | [managing-docker-clusters-using-mesos-and-marathon](http://www.jdon.com/artichect/managing-docker-clusters-using-mesos-and-marathon.html) 97 | 98 | ### Built as Docker Orchestration 99 | -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | - [1 01.虚拟化 [8]](/01.虚拟化/README.md) 2 | - [1.1 Ceph](/01.虚拟化/Ceph/README.md) 3 | 4 | - [1.2 Docker [6]](/01.虚拟化/Docker/README.md) 5 | - [1.2.1 01.配置部署 [3]](/01.虚拟化/Docker/01.配置部署/README.md) 6 | - [1.2.1.1 Docker Compose](/01.虚拟化/Docker/01.配置部署/Docker%20Compose.md) 7 | - [1.2.1.2 安装与配置](/01.虚拟化/Docker/01.配置部署/安装与配置.md) 8 | - [1.2.1.3 容器编排](/01.虚拟化/Docker/01.配置部署/容器编排.md) 9 | - [1.2.2 02.镜像与容器 [2]](/01.虚拟化/Docker/02.镜像与容器/README.md) 10 | - [1.2.2.1 Dockerfile 配置](/01.虚拟化/Docker/02.镜像与容器/Dockerfile%20配置.md) 11 | - [1.2.2.2 镜像体积优化](/01.虚拟化/Docker/02.镜像与容器/镜像体积优化.md) 12 | - [1.2.3 03.数据卷与存储](/01.虚拟化/Docker/03.数据卷与存储/README.md) 13 | 14 | - [1.2.4 04.网络 [2]](/01.虚拟化/Docker/04.网络/README.md) 15 | - [1.2.4.1 网络模型](/01.虚拟化/Docker/04.网络/网络模型.md) 16 | - [1.2.4.2 网络配置](/01.虚拟化/Docker/04.网络/网络配置.md) 17 | - 1.2.5 09.Swarm [2] 18 | - [1.2.5.1 Swarm](/01.虚拟化/Docker/09.Swarm/Swarm.md) 19 | - [1.2.5.2 基于 Docker Swarm 的微服务编排与监控](/01.虚拟化/Docker/09.Swarm/基于%20Docker%20Swarm%20的微服务编排与监控.md) 20 | - 1.2.6 99~参考资料 [2] 21 | - [1.2.6.1 《Docker 从入门到实践》](/01.虚拟化/Docker/99~参考资料/2020-《Docker%20从入门到实践》/README.md) 22 | 23 | - [1.2.6.2 无限咪咪 Docker 与 Kubernetes小记](/01.虚拟化/Docker/99~参考资料/2023-无限咪咪-Docker%20与%20Kubernetes小记.md) 24 | - [1.3 Focker [4]](/01.虚拟化/Focker/README.md) 25 | - [1.3.1 Bash](/01.虚拟化/Focker/Bash.md) 26 | - [1.3.2 C](/01.虚拟化/Focker/C.md) 27 | - [1.3.3 Go](/01.虚拟化/Focker/Go.md) 28 | - [1.3.4 Python](/01.虚拟化/Focker/Python.md) 29 | - [1.4 Linux 资源隔离 [5]](/01.虚拟化/Linux%20资源隔离/README.md) 30 | - [1.4.1 AUFS](/01.虚拟化/Linux%20资源隔离/AUFS.md) 31 | - [1.4.2 CGroups](/01.虚拟化/Linux%20资源隔离/CGroups.md) 32 | - [1.4.3 LXC](/01.虚拟化/Linux%20资源隔离/LXC.md) 33 | - [1.4.4 Namespaces](/01.虚拟化/Linux%20资源隔离/Namespaces.md) 34 | - [1.4.5 虚拟内核](/01.虚拟化/Linux%20资源隔离/虚拟内核.md) 35 | - [1.5 OCI](/01.虚拟化/OCI/README.md) 36 | 37 | - [1.6 Xen [2]](/01.虚拟化/Xen/README.md) 38 | - [1.6.1 快速开始](/01.虚拟化/Xen/快速开始.md) 39 | - [1.6.2 虚拟机创建](/01.虚拟化/Xen/虚拟机创建.md) 40 | - [1.7 存储虚拟化](/01.虚拟化/存储虚拟化/README.md) 41 | 42 | - [1.8 容器 [2]](/01.虚拟化/容器/README.md) 43 | - [1.8.1 云厂商的容器技术](/01.虚拟化/容器/云厂商的容器技术.md) 44 | - [1.8.2 容器技术简史](/01.虚拟化/容器/容器技术简史.md) 45 | - [2 02.集群编排 [6]](/02.集群编排/README.md) 46 | - [2.1 Mesos [1]](/02.集群编排/Mesos/README.md) 47 | - [2.1.1 部署与配置](/02.集群编排/Mesos/部署与配置.md) 48 | - [2.2 OAM [1]](/02.集群编排/OAM/README.md) 49 | - [2.2.1 协议规范](/02.集群编排/OAM/协议规范.md) 50 | - [2.3 Yarn](/02.集群编排/Yarn/README.md) 51 | 52 | - [2.4 云上安全](/02.集群编排/云上安全/README.md) 53 | 54 | - [2.5 云计算](/02.集群编排/云计算.md) 55 | - [2.6 资源调度 [4]](/02.集群编排/资源调度/README.md) 56 | - [2.6.1 任务调度](/02.集群编排/资源调度/任务调度.md) 57 | - [2.6.2 单机资源管理](/02.集群编排/资源调度/单机资源管理.md) 58 | - [2.6.3 数据调度](/02.集群编排/资源调度/数据调度.md) 59 | - [2.6.4 计算调度](/02.集群编排/资源调度/计算调度.md) 60 | - [3 03.K8s](/03.K8s/README.md) 61 | 62 | - [4 10.边缘计算 [1]](/10.边缘计算/README.md) 63 | - [4.1 开源项目梳理](/10.边缘计算/开源项目梳理.md) 64 | - [5 INTRODUCTION](/INTRODUCTION.md) -------------------------------------------------------------------------------- /01~虚拟化/Docker/01~配置部署/容器编排.md: -------------------------------------------------------------------------------- 1 | # Docker 容器编排 2 | 3 | # Compose 4 | 5 | # Swarm 6 | 7 | Routing Mesh 8 | 9 | ``` 10 | $ docker service create --publish : nginx 11 | 12 | $ docker service create --name my_web --replicas 3 --publish 8080:80 nginx 13 | ``` 14 | 15 | `/etc/docker/daemin.json` 16 | 17 | ```json 18 | { 19 | "metrics-addr": "127.0.0.1:9323", 20 | "experimental": true 21 | } 22 | ``` 23 | 24 | ```yaml 25 | # my global config 26 | global: 27 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 28 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 29 | # scrape_timeout is set to the global default (10s). 30 | 31 | # Attach these labels to any time series or alerts when communicating with 32 | # external systems (federation, remote storage, Alertmanager). 33 | external_labels: 34 | monitor: "codelab-monitor" 35 | 36 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 37 | rule_files: 38 | # - "first.rules" 39 | # - "second.rules" 40 | 41 | # A scrape configuration containing exactly one endpoint to scrape: 42 | # Here it's Prometheus itself. 43 | scrape_configs: 44 | # The job name is added as a label `job=` to any timeseries scraped from this config. 45 | - job_name: "prometheus" 46 | 47 | # metrics_path defaults to '/metrics' 48 | # scheme defaults to 'http'. 49 | 50 | static_configs: 51 | - targets: ["localhost:9090"] 52 | 53 | - job_name: "docker" 54 | # metrics_path defaults to '/metrics' 55 | # scheme defaults to 'http'. 56 | 57 | static_configs: 58 | - targets: ["localhost:9323"] 59 | ``` 60 | 61 | ```sh 62 | docker run -p 9090:9090 -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \ 63 | prom/prometheus 64 | ``` 65 | 66 | ```sh 67 | docker run -p 9090:9090 -v /prometheus-data \ 68 | prom/prometheus --config.file=/prometheus-data/prometheus.yml 69 | ``` 70 | 71 | # Stack 72 | 73 | Docker Compose is a Python project. Originally, there was a Python project known as fig which was used to parse fig.yml files to bring up - you guessed it - stacks of Docker containers. Everybody loved it, especially the Docker folks, so it got reincarnated as docker compose to be closer to the product. But it was still in Python, operating on top of the Docker Engine. 74 | 75 | Internally, it uses the Docker API to bring up containers according to a specification. You still have to install docker-compose separately to use it with Docker on your machine. 76 | 77 | The Docker Stack functionality, is included with the Docker engine. You don’t need to install additional packages to use it Deploying docker stacks is part of the swarm mode. It supports the same kinds of compose files, but the handling happens in Go code, inside of the Docker Engine. You also have to create a one-machine “swarm” before being able to use the stack command, but that’ not a big issue. 78 | 79 | Docker stack does not support docker-compose.yml files which are written according to the version 2 specification. It has to be the most recent one, which is 3 at the time of writing, while Docker Compose still can handle versions 2 and 3 without problems. 80 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/README.md: -------------------------------------------------------------------------------- 1 | # 容器技术与资源隔离 2 | 3 | 容器是一种轻量级的虚拟化技术,它不需要模拟硬件创建虚拟机。在 Linux 系统里面,使用到 Linux kernel 的 CGroups,Namespace(ipc,network,user,pid,mount),capability 等用于隔离运行环境和资源限制的技术。容器技术早就出现。例如 Solaris Zones 和 BSD jails 就是非 Linux 操作系统上的容器,而用于 Linux 的容器技术也有很多如 Linux-Vserver、OpenVZ 和 FreeVPS。虽然这些技术都已经成熟,但是这些解决方案还没有将它们的容器支持集成到主流 Linux 内核。总的来说,容器不等同于 Docker,容器更不是虚拟机。 4 | 5 | ![Docker & Linux Internals](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424143609.png) 6 | 7 | Linux CGroups 为一种名为 Linux 容器(LXC)的技术铺平了道路。LXC 实际上是我们今天所知的第一个实现容器的主要实现,利用 cgroup 和命名空间隔离来创建具有独立进程和网络空间的虚拟环境。从某种意义上说,这允许独立和隔离的用户空间。容器的概念直接来自 LXC。事实上,早期版本的 Docker 直接构建在 LXC 之上。Docker 最初目标是做一个特殊的 LXC 的开源系统,最后慢慢演变为它自己的一套容器运行时环境。Docker 基于 Linux kernel 的 CGroups,Namespace,UnionFileSystem 等技术封装成一种自定义的容器格式,用于提供一整套虚拟运行环境。毫无疑问,近些年来 Docker 已经成为了容器技术的代名词。 8 | 9 | ![Docker 与虚拟机](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424143632.png) 10 | 11 | # After `docker run` 12 | 13 | 通过下面命令运行一个 debian 容器,attach 到一个本机的命令行并运行/bin/bash。 14 | 15 | ```undefined 16 | docker run -i -t debian /bin/bash 17 | ``` 18 | 19 | 这个命令背后都做了什么? 20 | 21 | - 1.如果本机没有 debian 镜像,则会从你配置的 Registry 里面拉取一个 debian 的 latest 版本的镜像,跟你运行了`docker pull debian`效果一样。 22 | - 2.创建容器。跟运行`docker create`一样。 23 | - 3.给容器分配一个读写文件系统作为该容器的`final layer`,容器可以在它的文件系统创建和修改文件。 24 | - 4.Docker 为容器创建了一套网络接口,给容器分配一个 ip。默认情况下,容器可以通过默认网络连通到外部网络。 25 | - 5.Docker 启动容器并执行 `/bin/bash`。因为启动时指定了`-i -t`参数,容器是以交互模式运行且 attach 到本地终端,我们可以在终端上输入命令并看到输出。 26 | - 6.运行 exit 可以退出容器,但是此时容器并没有被删除,我们可以再次运行它或者删除它。 27 | 28 | 可以发现,容器的内核版本是跟宿主机一样的,不同的是容器的主机名是独立的,它默认用容器 ID 做主机名。我们运行`ps -ef`可以发现容器进程是隔离的,容器里面看不到宿主机的进程,而且它自己有 PID 为 1 的进程。此外,网络也是隔离的,它有独立于宿主机的 IP。文件系统也是隔离的,容器有自己的系统和软件目录,修改容器内的文件并不影响宿主机对应目录的文件。 29 | 30 | ```ruby 31 | root@stretch:/home/vagrant# uname -r 32 | 4.9.0-6-amd64 33 | root@stretch:/home/vagrant# docker run -it --name demo alpine /bin/ash 34 | / # uname -r ## 容器内 35 | 4.9.0-6-amd64 36 | 37 | / # ps -ef 38 | PID USER TIME COMMAND 39 | 1 root 0:00 /bin/ash 40 | 7 root 0:00 ps -ef 41 | 42 | / # ip a 43 | 1: lo: mtu 65536 qdisc noqueue state UNKNOWN qlen 1 44 | link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 45 | inet 127.0.0.1/8 scope host lo 46 | valid_lft forever preferred_lft forever 47 | 6: eth0@if7: mtu 1500 qdisc noqueue state UP 48 | link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff 49 | inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 50 | valid_lft forever preferred_lft forever 51 | ``` 52 | 53 | 这些隔离机制并不是 Docker 新开发的技术,而是依托 Linux kernel 以及一些已有的技术实现的,主要包括: 54 | 55 | - 隔离性,Linux Namespaces(Linux2.6.24 后引入):命名空间用于进程(PID)、网络(NET)、挂载点(MNT)、UTS、IPC 等隔离。容器内的应用只能在自己的命名空间中运行而且不会访问到命名空间之外。 56 | 57 | - 控制组,Linux Control Groups(CGroups):用于限制容器使用的资源,包括内存,CPU 等。使应用隔离运行的关键是让它们只使用你想要的资源。这样可以确保在机器上运行的容器都是良民(good multi-tenant citizens)。群组控制允许 Docker 分享或者限制容器使用硬件资源。 58 | 59 | - 便携性,Union File Systems:UnionFS 把多个目录结合成一个目录,对外使用,最上层目录为读写层(通常只有 1 个),下面可以有一个或多个只读层,见容器和镜像分层图。Docker 支持 OverlayFS,AUFS、DeviceMapper、btrfs 等联合文件系统,支持将不同目录挂载到同一个虚拟文件系统下的文件系统。 60 | 61 | - Container Format: Docker Engine 组合 Namespaces,CGroups 以及 UnionFS 包装为一个容器格式,默认格式为 libcontainer,后续可能会加入 BSD Jails 或 Solaris Zones 容器格式的支持。 62 | 63 | # Links 64 | 65 | - https://unit42.paloaltonetworks.com/making-containers-more-isolated-an-overview-of-sandboxed-container-technologies 66 | 67 | - https://jiajially.gitbooks.io/dockerguide/content/dockerCoreNS.html 提取其中的命令与原理解析 68 | -------------------------------------------------------------------------------- /01~虚拟化/容器/README.md: -------------------------------------------------------------------------------- 1 | # 容器虚拟化 2 | 3 | **Machine-level virtualization**, such as [KVM](https://www.linux-kvm.org/) and [Xen](https://www.xenproject.org/), exposes virtualized hardware to a guest kernel via a Virtual Machine Monitor (VMM). This virtualized hardware is generally enlightened (paravirtualized) and additional mechanisms can be used to improve the visibility between the guest and host (e.g. balloon drivers, paravirtualized spinlocks). Running containers in distinct virtual machines can provide great isolation, compatibility and performance (though nested virtualization may bring challenges in this area), but for containers it often requires additional proxies and agents, and may require a larger resource footprint and slower start-up times. 4 | 5 | [![Machine-level virtualization](https://github.com/google/gvisor/raw/master/g3doc/Machine-Virtualization.png)](https://github.com/google/gvisor/blob/master/g3doc/Machine-Virtualization.png) 6 | 7 | **Rule-based execution**, such as [seccomp](https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt), [SELinux](https://selinuxproject.org/) and [AppArmor](https://wiki.ubuntu.com/AppArmor), allows the specification of a fine-grained security policy for an application or container. These schemes typically rely on hooks implemented inside the host kernel to enforce the rules. If the surface can be made small enough (i.e. a sufficiently complete policy defined), then this is an excellent way to sandbox applications and maintain native performance. However, in practice it can be extremely difficult (if not impossible) to reliably define a policy for arbitrary, previously unknown applications, making this approach challenging to apply universally. 8 | 9 | [![Rule-based execution](https://github.com/google/gvisor/raw/master/g3doc/Rule-Based-Execution.png)](https://github.com/google/gvisor/blob/master/g3doc/Rule-Based-Execution.png) 10 | 11 | Rule-based execution is often combined with additional layers for defense-in-depth. 12 | 13 | **gVisor** provides a third isolation mechanism, distinct from those mentioned above. 14 | 15 | gVisor intercepts application system calls and acts as the guest kernel, without the need for translation through virtualized hardware. gVisor may be thought of as either a merged guest kernel and VMM, or as seccomp on steroids. This architecture allows it to provide a flexible resource footprint (i.e. one based on threads and memory mappings, not fixed guest physical resources) while also lowering the fixed costs of virtualization. However, this comes at the price of reduced application compatibility and higher per-system call overhead. 16 | 17 | [![gVisor](https://github.com/google/gvisor/raw/master/g3doc/Layers.png)](https://github.com/google/gvisor/blob/master/g3doc/Layers.png) 18 | 19 | On top of this, gVisor employs rule-based execution to provide defense-in-depth (details below). 20 | 21 | gVisor's approach is similar to [User Mode Linux (UML)](http://user-mode-linux.sourceforge.net/), although UML virtualizes hardware internally and thus provides a fixed resource footprint. 22 | 23 | 与虚拟机相比,容器更轻量且速度更快,因为它利用了 Linux 底层操作系统在隔离的环境中运行。虚拟机的 hypervisor 创建了一个非常牢固的边界,以防止应用程序突破它,而容器的边界不那么强大。另一个区别是,由于 Namespace 和 Cgroups 功能仅在 Linux 上可用,因此容器无法在其他操作系统上运行;Docker 实际上使用了一个技巧,并在非 Linux 操作系统上安装 Linux 虚拟机,然后在虚拟机内运行容器。 24 | 25 | 虽然大多数 IT 行业正在采用基于容器的基础架构(云原生解决方案),但必须了解该技术的局限性。传统容器(如 Docker,Linux Containers(LXC)和 Rocket(rkt))并不是真正的沙箱,因为它们共享主机操作系统内核。它们具有资源效率,但攻击面和破坏的潜在影响仍然很大,特别是在多租户云环境中,共同定位属于不同客户的容器。 26 | 27 | 当主机操作系统为每个容器创建虚拟化用户空间时,问题的根源是容器之间的弱分离。一直致力于设计真正的沙盒容器的研究和开发。大多数解决方案重新构建容器之间的边界以加强隔离。譬如来自 IBM,Google,Amazon 和 OpenStack 的四个独特项目,这些项目使用不同的技术来实现相同的目标,为容器创建更强的隔离。 28 | 29 | - IBM Nabla 在 Unikernels 之上构建容器 30 | - Google gVisor 创建了一个用于运行容器的专用客户机内核 31 | - Amazon Firecracker 是一个用于沙盒应用程序的极轻量级管理程序 32 | - OpenStack 将容器放置在针对容器编排平台优化的专用 VM 中 33 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/CGroups.md: -------------------------------------------------------------------------------- 1 | # CGroups 2 | 3 | 2006 年,Google 的工程师发明了 Linux 控制组,缩写为 CGroups。这是 Linux 内核的一项功能,可隔离和控制用户进程的资源使用情况。这些进程可以放入命名空间,实质上是共享相同资源限制的进程集合。计算机可以有多个命名空间,每个命名空间都具有内核强制执行的资源属性。 4 | 5 | 我们可以管理每个命名空间的资源分配,以便限制一组进程可以使用的总 CPU,RAM 等的数量。例如,后台日志聚合应用程序可能需要限制其资源,以免意外地压倒它正在记录的实际服务器。虽然不是原始功能,但 Linux 中的 CGroups 最终被重新设计为包含命名空间隔离的功能。命名空间隔离本身并不新鲜,Linux 已经有多种命名空间隔离。一个常见的例子是进程隔离,它将每个单独的进程分开并防止诸如共享内存之类的事情。 6 | 7 | CGroups 隔离是一种更高级别的隔离,可确保 CGroups 命名空间中的进程独立于其他命名空间中的进程。 8 | 9 | - PID(进程标识符)命名空间:这可确保一个命名空间内的进程不知道其他命名空间中的进程。 10 | - 网络命名空间:隔离网络接口控制器,iptables,路由表和其他低级网络工具。 11 | - 挂载命名空间:已挂载文件系统,因此命名空间的文件系统范围仅限于已挂载的目录。 12 | - 用户名空间:将命名空间内的用户限制为仅限该命名空间,并避免跨命名空间的用户 ID 冲突。 13 | 14 | # cgroup-bin 15 | 16 | 通过工具 cgroup-bin (`sudo apt-get install cgroup-bin`)可以创建 CGroup 并进入该 CGroup 执行命令。 17 | 18 | ```s 19 | root@host:/home/vagrant# cgcreate -a vagrant -g cpu:cg1 20 | root@host:/home/vagrant# ls /sys/fs/cgroup/cpu/cg1/ 21 | cgroup.clone_children cpu.cfs_period_us cpu.shares cpuacct.stat cpuacct.usage_all cpuacct.usage_percpu_sys cpuacct.usage_sys notify_on_release 22 | cgroup.procs cpu.cfs_quota_us cpu.stat cpuacct.usage cpuacct.usage_percpu cpuacct.usage_percpu_user cpuacct.usage_user tasks 23 | ``` 24 | 25 | cpu.cfs_period_us 和 cpu.cfs_quota_us,它们分别用来限制该组中的所有进程在单位时间里可以使用的 cpu 时间,这里的 cfs(Completely Fair Scheduler) 是完全公平调度器的意思。cpu.cfs_period_us 是时间周期,默认为 100000,即 100 毫秒。而 cpu.cfs_quota_us 是在时间周期内可以使用的时间,默认为-1 即无限制。cpu.shares 用于限制 cpu 使用的,它用于控制各个组之间的配额。比如组 cg1 的 cpu.shares 为 1024,组 cg2 的 cpu.shares 也是 1024,如果都有进程在运行则它们都可以使用最多 50%的限额。如果 cg2 组内进程比较空闲,那么 cg1 组可以将使用几乎整个 cpu,tasks 存储的是该组里面的进程 ID。 26 | 27 | 我们先在默认的分组里面运行一个死循环程序 loop.py,因为默认分组/sys/fs/cgroup/cpu/cpu.cfs_period_us 和 cfs_quota_us 是默认值,所以是没有限制 cpu 使用的。可以发现 1 个 cpu us 立马接近 100%了。 28 | 29 | ```py 30 | # loop.py 31 | while True: pass 32 | ``` 33 | 34 | 设置 cg1 组的 cfs_quota_us 位 50000,即表示该组内进程最多使用 50%的 cpu 时间,运行 cgexec 命令进入 cg1 的 cpu 组,然后运行 loop.py,可以发现 cpu us 在 50%以内了,此时也可以在 tasks 文件中看到我们刚刚 cgexec 创建的进程 ID。 35 | 36 | ```s 37 | root@host:/home/vagrant# echo 50000 > /sys/fs/cgroup/cpu/cg1/cpu.cfs_quota_us 38 | root@host:/home/vagrant# cgexec -g cpu:cg1 /bin/bash 39 | ``` 40 | 41 | Docker 里面要限制内存和 CPU 使用,可以在启动时指定相关参数即可。比如限制 cpu 使用率,加 cpu-period 和 cpu-quota 参数,限制执行的 cpu 核,加--cpuset-cpus 参数。限制内存使用,加--memory 参数。当然,我们可以看到在 /sys/fs/cgroup/cpu/docker/目录下有个以 containerid 为名的分组,该分组下面的 cpu.cfs_period_us 和 cpu.cfs_quota_us 的值就是我们在启动容器时指定的值。 42 | 43 | ```s 44 | root@host:/home/vagrant# docker run -i -t --cpu-period=100000 --cpu-quota=50000 --memory=512000000 alpine /bin/ash 45 | ``` 46 | 47 | # Capabilities 48 | 49 | 我们在启动容器时会时常看到这样的参数 `--cap-add=NET_ADMIN`,这是用到了 Linux 的 capability 特性。capability 是为了实现更精细化的权限控制而加入的。我们以前熟知通过设置文件的 SUID 位,这样非 root 用户的可执行文件运行后的 euid 会成为文件的拥有者 ID,比如 passwd 命令运行起来后有 root 权限,有 SUID 权限的可执行文件如果存在漏洞会有安全风险。(查看文件的 capability 的命令为 filecap -a,而查看进程 capability 的命令为 pscap -a,pscap 和 filecap 工具需要安装 libcap-ng-utils 这个包)。 50 | 51 | 对于 capability,可以看一个简单的例子便于理解。如 Debian 系统中自带的 ping 工具,它是有设置 SUID 位的。这里拷贝 ping 重命名为 anotherping,anotherping 的 SUID 位没有设置,运行会提示权限错误。这里,我们只要将其加上 cap_net_raw 权限即可,不需要设置 SUID 位那么大的权限。 52 | 53 | ```s 54 | vagrant@host:~$ ls -ls /bin/ping 55 | 60 -rwsr-xr-x 1 root root 61240 Nov 10 2016 /bin/ping 56 | vagrant@host:~$ cp /bin/ping anotherping 57 | vagrant@host:~$ ls -ls anotherping 58 | 60 -rwxr-xr-x 1 vagrant vagrant 61240 May 19 10:18 anotherping 59 | vagrant@host:~$ ./anotherping -c1 yue.uu.163.com 60 | ping: socket: Operation not permitted 61 | vagrant@host:~$ sudo setcap cap_net_raw+ep ./anotherping 62 | vagrant@host:~$ ./anotherping -c1 yue.uu.163.com 63 | PING yue.uu.163.com (59.111.137.252) 56(84) bytes of data. 64 | 64 bytes from 59.111.137.252 (59.111.137.252): icmp_seq=1 ttl=63 time=53.9 ms 65 | 66 | --- yue.uu.163.com ping statistics --- 67 | 1 packets transmitted, 1 received, 0% packet loss, time 0ms 68 | rtt min/avg/max/mdev = 53.919/53.919/53.919/0.000 ms 69 | ``` 70 | -------------------------------------------------------------------------------- /02~集群编排/Mesos/README.md: -------------------------------------------------------------------------------- 1 | # Mesos 2 | 3 | > Mesos is a meta, framework scheduler rather than an application scheduler like YARN. 4 | 5 | 从各个常用的组件的协调角度,我们可以来看如下这张图: 6 | 7 | 而从 Mesos 本身的功能模型,即 Master 与 Slave 的模型之间的关系的角度来看的话,可以得到如下模型: 8 | 9 | ![](http://i.stack.imgur.com/Gqhrx.jpg) 10 | 11 | Slave 是运行在物理或虚拟服务器上的 Mesos 守护进程,是 Mesos 集群的一部分。Framework 由调度器(Scheduler)应用程序和任务执行器(Executor)组成,被注册到 Mesos 以使用 Mesos 集群中的资源。 12 | 13 | - Slave 1 向 Master 汇报其空闲资源:4 个 CPU、4GB 内存。然后,Master 触发分配策略模块,得到的反馈是 Framework 1 要请求全部可用资源。 14 | - Master 向 Framework 1 发送资源邀约,描述了 Slave 1 上的可用资源。 15 | - Framework 的调度器(Scheduler)响应 Master,需要在 Slave 上运行两个任务,第一个任务分配<2 CPUs, 1 GB RAM>资源,第二个任务分配<1 CPUs, 2 GB RAM>资源。 16 | - 最后,Master 向 Slave 下发任务,分配适当的资源给 Framework 的任务执行器(Executor),接下来由执行器启动这两个任务(如图中虚线框所示)。此时,还有 1 个 CPU 和 1GB 的 RAM 尚未分配,因此分配模块可以将这些资源供给 Framework 2。 17 | 18 | ## Advantage 19 | 20 | - 效率:这是最显而易见的好处,也是 Mesos 社区和 Mesosphere 经常津津乐道的。 21 | 22 | 上图来自 Mesosphere 网站,描绘出 Mesos 为效率带来的好处。如今,在大多数数据中心中,服务器的静态分区是常态,即使使用最新的应用程序,如 Hadoop。这时常令人担忧的是,当不同的应用程序使用相同的节点时,调度相互冲突,可用资源互相争抢。静态分区本质上是低效的,因为经常会面临,其中一个分区已经资源耗尽,而另一个分区的资源却没有得到充分利用,而且没有什么简单的方法能跨分区集群重新分配资源。使用 Mesos 资源管理器仲裁不同的调度器,我们将进入动态分区/弹性共享的模式,所有应用程序都可以使用节点的公共池,安全地、最大化地利用资源。一个经常被引用的例子是 Slave 节点通常运行 Hadoop 作业,在 Slave 空闲阶段,动态分配给他们运行批处理作业,反之亦然。值得一提的是,这其中的某些环节可以通过虚拟化技术,如 VMware vSphere 的 分布式资源调度(DRS) 来完成。然而,Mesos 具有更精细的粒度,因为 Mesos 在应用层而不是机器层分配资源,通过容器而不是整个虚拟机(VM)分配任务。前者能够为每个应用程序的特殊需求做考量,应用程序的调度器知道最有效地利用资源; 后者能够更好地“装箱”,运行一个任务,没有必要实例化一整个虚拟机,其所需的进程和二进制文件足矣。 23 | 24 | - 敏捷:与效率和利用率密切相关,这实际上是我认为最重要的好处。往往,效率解决的是“如何花最少的钱最大化数据中心的资源”,而敏捷解决的是“如何快速用上手头的资源。” 正如我和我的同事 [Tyler Britten](https://twitter.com/vmtyler) 经常指出,IT 的存在是帮助企业赚钱和省钱的;那么如何通过技术帮助我们迅速创收,是我们要达到的重要指标。这意味着要确保关键应用程序不能耗尽所需资源,因为我们无法为应用提供足够的基础设施,特别是在数据中心的其他地方都的资源是收费情况下。 25 | 26 | - 可扩展性:为可扩展而设计,这是我真心欣赏 Mesos 架构的地方。这一重要属性使数据可以指数级增长、分布式应用可以水平扩展。我们的发展已经远远超出了使用巨大的整体调度器或者限定群集节点数量为 64 的时代,足矣承载新形式的应用扩张。Mesos 可扩展设计的关键之处是采用两级调度架构。使用 Framework 代理任务的实际调度,Master 可以用非常轻量级的代码实现,更易于扩展集群发展的规模。因为 Master 不必知道所支持的每种类型的应用程序背后复杂的调度逻辑。此外,由于 Master 不必为每个任务做调度,因此不会成为容量的性能瓶颈,而这在为每个任务或者虚拟机做调度的整体调度器中经常发生。 27 | 28 | - 模块化:对我来说,预测任何开源技术的健康发展,很大程度上取决于围绕该项目的生态系统。我认为 Mesos 项目前景很好,因为其设计具有包容性,可以将功能插件化,比如分配策略、隔离机制和 Framework。将容器技术,比如 Docker 和 Rocket 插件化的好处是显而易见。但是我想在此强调的是围绕 Framework 建设的生态系统。将任务调度委托给 Framework 应用程序,以及采用插件架构,通过 Mesos 这样的设计,社区创造了能够让 Mesos 问鼎数据中心资源管理的生态系统。因为每接入一种新的 Framework,Master 无需为此编码,Slave 模块可以复用,使得在 Mesos 所支持的宽泛领域中,业务迅速增长。相反,开发者可以专注于他们的应用和 Framework 的选择。当前而且还在不断地增长着的 Mesos Framework 列表参见 [此处](http://mesos.apache.org/documentation/latest/mesos-frameworks/) 以及下图: 29 | 30 | ### 资源分配 31 | 32 | 为了实现在同一组 Slave 节点集合上运行多任务这一目标,Mesos 使用了隔离模块,该模块使用了一些应用和进程隔离机制来运行这些任务。不足为奇的是,虽然可以使用虚拟机隔离实现隔离模块,但是 Mesos 当前模块支持的是容器隔离。Mesos 早在 2009 年就用上了 Linux 的容器技术,如 cgroups 和 Solaris Zone,时至今日这些仍然是默认的。然而,Mesos 社区增加了 Docker 作为运行任务的隔离机制。不管使用哪种隔离模块,为运行特定应用程序的任务,都需要将执行器全部打包,并在已经为该任务分配资源的 Slave 服务器上启动。当任务执行完毕后,容器会被“销毁”,资源会被释放,以便可以执行其他任务。 33 | 34 | 我们来更深入地研究一下资源邀约和分配策略,因为这对 Mesos 管理跨多个 Framework 和应用的资源,是不可或缺的。我们前面提到资源邀约的概念,即由 Master 向注册其上的 Framework 发送资源邀约。每次资源邀约包含一份 Slave 节点上可用的 CPU、RAM 等资源的列表。Master 提供这些资源给它的 Framework,是基于分配策略的。分配策略对所有的 Framework 普遍适用,同时适用于特定的 Framework。Framework 可以拒绝资源邀约,如果它不满足要求,若此,资源邀约随即可以发给其他 Framework。由 Mesos 管理的应用程序通常运行短周期的任务,因此这样可以快速释放资源,缓解 Framework 的资源饥饿;Slave 定期向 Master 报告其可用资源,以便 Master 能够不断产生新的资源邀约。另外,还可以使用诸如此类的技术,每个 Fraamework 过滤不满足要求的资源邀约、Master 主动废除给定周期内一直没有被接受的邀约。 35 | 36 | 分配策略有助于 Mesos Master 判断是否应该把当前可用资源提供给特定的 Framework,以及应该提供多少资源。关于 Mesos 中使用资源分配以及可插拔的分配模块,实现非常细粒度的资源共享,会单独写一篇文章。言归正传,Mesos 实现了公平共享和严格优先级(这两个概念我会在资源分配那篇讲)分配模块,确保大部分用例的最佳资源共享。已经实现的新分配模块可以处理大部分之外的用例。 37 | 38 | # Links 39 | 40 | - [mesos-docker-tutorial-how-to-build-your-own-framework](https://www.voxxed.com/blog/2014/12/mesos-docker-tutorial-how-to-build-your-own-framework/) 41 | 42 | - [Getting Started with Apache Mesos](https://github.com/shekhargulati/52-technologies-in-2016/blob/master/18-mesos/README.md) 43 | 44 | - [- [回顾 Java 发展,看 Docker 与 Mesos](http://dockone.io/article/1152) 45 | 46 | - [InfoQ - 深入浅出 Mesos](http://www.infoq.com/cn/minibooks/analyse-mesos) 47 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cloud-Series 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 38 | 40 | 45 | 46 |
47 | 64 | 97 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 142 | 143 | 144 | 145 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/02~镜像与容器/Dockerfile 配置.md: -------------------------------------------------------------------------------- 1 | # Dockerfile 配置 2 | 3 | ```Dockerfile 4 | # Replace latest with a pinned version tag from https://hub.docker.com/_/alpine 5 | # 6 | # We suggest using the major.minor tag, not major.minor.patch. 7 | FROM alpine:latest 8 | 9 | # Non-root user for security purposes. 10 | # 11 | # UIDs below 10,000 are a security risk, as a container breakout could result 12 | # in the container being ran as a more privileged user on the host kernel with 13 | # the same UID. 14 | # 15 | # Static GID/UID is also useful for chown'ing files outside the container where 16 | # such a user does not exist. 17 | RUN addgroup -g 10001 -S nonroot && adduser -u 10000 -S -G nonroot -h /home/nonroot nonroot 18 | 19 | # Install packages here with `apk add --no-cache`, copy your binary 20 | # into /sbin/, etc. 21 | 22 | # Tini allows us to avoid several Docker edge cases, see https://github.com/krallin/tini. 23 | RUN apk add --no-cache tini 24 | ENTRYPOINT ["/sbin/tini", "--", "myapp"] 25 | # Replace "myapp" above with your binary 26 | 27 | # bind-tools is needed for DNS resolution to work in *some* Docker networks, but not all. 28 | # This applies to nslookup, Go binaries, etc. If you want your Docker image to work even 29 | # in more obscure Docker environments, use this. 30 | RUN apk add --no-cache bind-tools 31 | 32 | # Use the non-root user to run our application 33 | USER nonroot 34 | 35 | # Default arguments for your app (remove if you have none): 36 | CMD ["--foo", "1", "--bar=2"] 37 | ``` 38 | 39 | # 多阶段构建 40 | 41 | 随着 17.05 版本的发布,Docker 对于镜像构建这块也作了一项重要更新,那就是 multi-stage build(多阶段构建),这有助于方便源代码控制,减小镜像体积。 42 | 43 | ```Dockerfile 44 | # First stage: complete build environment 45 | FROM maven:3.5.0-jdk-8-alpine AS builder 46 | # add pom.xml and source code 47 | ADD ./pom.xml pom.xml 48 | ADD ./src src/ 49 | # package jar 50 | RUN mvn clean package 51 | 52 | # Second stage: minimal runtime environment 53 | From openjdk:8-jre-alpine 54 | # copy jar from the first stage 55 | COPY --from=builder target/msb-1.0.jar msb.jar 56 | # run jar 57 | CMD ["java", "-jar", "msb.jar"] 58 | ``` 59 | 60 | 对于 multi-stage build,其关键点主要有两点: 61 | 62 | 在前面阶段的 FROM 指令后面增加了一个 AS 参数,可为该构建阶段命名,便于后续构建阶段引用,格式如下: 63 | 64 | ``` 65 | FROM image[:tag | @digest] AS stage 66 | ``` 67 | 68 | 在后续阶段的 COPY 指令后面增加了--from 参数,指明引用前面哪一个构建阶段的成果,格式如下: 69 | 70 | ``` 71 | COPY --from=stage ... 72 | ``` 73 | 74 | 同理,多阶段构建同样可以很方便地将多个彼此依赖的项目通过一个 Dockerfile 就可轻松构建出期望的容器镜像,而不用担心镜像太大、源码泄露等风险。 75 | 76 | # 安全配置 77 | 78 | 容器安全是一个广泛的问题空间,有很多低垂的果实可以收获来降低风险。一个好的出发点是在编写 Dockerfiles 时遵循一些规则。 79 | 80 | ## Do not store secrets in environment variables 81 | 82 | 密钥分发是一个棘手的问题,而且很容易做错。对于容器化的应用,人们可以通过挂载卷从文件系统中浮出水面,或者通过环境变量更方便地浮出水面。使用 ENV 来存储密钥是不好的做法,因为 Dockerfiles 通常是和应用一起分发的,所以和在代码中硬编码密钥没有区别。 83 | 84 | ```sh 85 | secrets_env = [ 86 | "passwd", 87 | "password", 88 | "pass", 89 | # "pwd", can't use this one 90 | "secret", 91 | "key", 92 | "access", 93 | "api_key", 94 | "apikey", 95 | "token", 96 | "tkn" 97 | ] 98 | 99 | deny[msg] { 100 | input[i].Cmd == "env" 101 | val := input[i].Value 102 | contains(lower(val[_]), secrets_env[_]) 103 | msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val]) 104 | } 105 | ``` 106 | 107 | ## 仅使用可信镜像 108 | 109 | 容器化应用的供应链攻击也将来自用于构建容器本身的层级。主要的罪魁祸首显然是使用的基础映像。不受信任的基础镜像是一种高风险,应尽可能避免使用。Docker 为大多数常用的操作系统和应用程序提供了一套官方基础镜像。通过使用它们,我们利用与 Docker 本身的某种责任分担,最大限度地降低了泄露的风险。 110 | 111 | ```sh 112 | deny[msg] { 113 | input[i].Cmd == "from" 114 | val := split(input[i].Value[0], "/") 115 | count(val) > 1 116 | msg = sprintf("Line %d: use a trusted base image", [i]) 117 | } 118 | ``` 119 | 120 | 这个规则是针对 DockerHub 的官方镜像调整的,信任的定义取决于你的上下文,相应地修改这个规则。 121 | 122 | ## 避免使用 latest 标签 123 | 124 | 锁定基础镜像的版本,可以让你对你正在构建的容器的可预测性放心一些。如果你依赖最新版本,你可能会默默地继承更新的包,在最好的最坏的情况下可能会影响你的应用程序的可靠性,在最坏的最坏的情况下可能会引入一个漏洞。 125 | 126 | ```sh 127 | deny[msg] { 128 | input[i].Cmd == "from" 129 | val := split(input[i].Value[0], ":") 130 | contains(lower(val[1]), "latest"]) 131 | msg = sprintf("Line %d: do not use 'latest' tag for base images", [i]) 132 | } 133 | ``` 134 | 135 | ## 避免使用 curl 136 | 137 | 从互联网上拉东西,然后用管道输送到 shell 中,这是最糟糕的。不幸的是,这是一个普遍的解决方案,以简化软件的安装。 138 | 139 | ```sh 140 | $ wget https://cloudberry.engineering/absolutely-trustworthy.sh | sh 141 | ``` 142 | 143 | 供应链攻击的风险是一样的框架,归根结底是信任。如果你真的要使用 curl bash,请正确使用。使用一个可信的源头、安全连接、验证您所下载内容的真实性和完整性。 144 | 145 | ```sh 146 | deny[msg] { 147 | input[i].Cmd == "run" 148 | val := concat(" ", input[i].Value) 149 | matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1) 150 | count(matches) > 0 151 | msg = sprintf("Line %d: Avoid curl bashing", [i]) 152 | } 153 | ``` 154 | 155 | ## 不要升级系统包 156 | 157 | 这可能有点牵强,但道理如下:你要将软件依赖的版本固定下来,如果你进行 apt-get 升级,你将有效地将它们全部升级到最新版本。如果你确实升级了,而且你使用最新的标签作为基础镜像,你就会放大你的依赖树的不可预测性。你要做的是将基础镜像的版本钉在 apt/apk 更新上。 158 | 159 | ```sh 160 | upgrade_commands = [ 161 | "apk upgrade", 162 | "apt-get upgrade", 163 | "dist-upgrade", 164 | ] 165 | 166 | deny[msg] { 167 | input[i].Cmd == "run" 168 | val := concat(" ", input[i].Value) 169 | contains(val, upgrade_commands[_]) 170 | msg = sprintf(“Line: %d: Do not upgrade your system packages", [i]) 171 | } 172 | ``` 173 | 174 | ## 避免使用 ADD 175 | 176 | ADD 命令有一个小特点,就是你可以把它指向一个远程的 url,它就会在构建的时候获取内容。 177 | 178 | ```sh 179 | ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz 180 | ``` 181 | 182 | 具有讽刺意味的是,官方文档建议使用 curl bashing 代替。从安全角度来看,同样的建议也适用:不要。先获取你需要的任何内容,验证后再复制。但如果你真的必须这样做,请通过安全连接使用可信的来源。 183 | 184 | ```sh 185 | deny[msg] { 186 | input[i].Cmd == "add" 187 | msg = sprintf("Line %d: Use COPY instead of ADD", [i]) 188 | } 189 | ``` 190 | 191 | ## 不要使用 root 192 | 193 | 容器中的 root 和主机上的 root 是一样的,但是受到 docker 守护进程配置的限制。不管有什么限制,如果一个行为者突破了容器,他仍然能够找到一种方法来获得对主机的完全访问。当然这并不理想,你的威胁模型不能忽视以 root 身份运行所带来的风险。因此最好总是指定一个用户。 194 | 195 | ```sh 196 | USER hopefullynotroot 197 | ``` 198 | 199 | 请注意,在 Dockerfile 中明确设置用户只是一层防御,并不能解决整个以 root 身份运行的问题。相反,人们可以采取深度防御的方法,并在整个堆栈中进一步缓解:严格配置 docker 守护进程或使用无根容器解决方案,限制运行时的配置(如果可能的话禁止 --privileged,等等),等等。 200 | 201 | ```sh 202 | any_user { 203 | input[i].Cmd == "user" 204 | } 205 | 206 | deny[msg] { 207 | not any_user 208 | msg = "Do not run as root, use USER instead" 209 | } 210 | ``` 211 | 212 | ## 不要使用 sudo 213 | 214 | ```sh 215 | deny[msg] { 216 | input[i].Cmd == "run" 217 | val := concat(" ", input[i].Value) 218 | contains(lower(val), "sudo") 219 | msg = sprintf("Line %d: Do not use 'sudo' command", [i]) 220 | } 221 | ``` 222 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/09~Swarm/基于 Docker Swarm 的微服务编排与监控.md: -------------------------------------------------------------------------------- 1 | # 基于 Docker Swarm 的(简单)微服务编排与治理 2 | 3 | 本文更多着眼于应用层的微服务架构,对于存储层/虚拟化层则另文讨论目前正在着手进行 K8s 迁移 4 | 5 | 应用服务本身尽可能无状态化 6 | 7 | 我司只有不到二十位开发人员,尚无专业的运维或者 SRE,希望以最小的代价满足应用部署与线上监控的目标。 8 | 9 | 使用 Docker 进行自动化部署,使用 Docker Swarm 进行集群管理与自动负载均衡,使用 Portainer 进行界面化的容器管理与监控,使用 Declarative Crawler 进行应用级别的监控。 10 | 11 | 涉及到静态页面、Node.js、Java、Python 12 | 13 | 使用 Caddy 作为反向代理与请求路口,使用 Jenkins 作为持续集成构建服务,使用 MySQL/TiDB 作为核心关系型数据库,使用 Redis 作为缓存数据库,使用 ElasticSearch/Kibana 作为搜索引擎支撑,使用 Presto 作为跨数据库查询层,使用 FileBeat 进行日志搜集,使用 Kafka / Kafka Streaming 作为消息队列以及简易流处理工具。 14 | 15 | 在早期的基础设施架构中我们只是做了 RAID,然后使用 Xen 对物理服务器进行了虚拟化操作,在虚拟机层面进行了容灾备份。除了专门的数据存储服务器以及反向代理服务器是由统一管理,其他各个产品线的应用都是由开发人员自己登陆可用的服务器进行部署;对于同一台机器需要部署多个应用的情况,我们是使用 tmux 开启多个 Session,这种模式对于调试还是非常友好的。这就导致了本身基础设施的混乱,以及应用的不可控;譬如尚未做隔离的情况下,某个应用的崩溃或者误操作可能会导致其所在的虚拟机崩溃,最终导致该虚拟机承载的其他服务或者其他依赖服务连锁崩溃。 16 | 17 | 另一个笔者发现,在这种朴素的模式中,往往部署所需要的配置是直接后来服务器增加到了十余台,线上应用的可用性与整个 IT 资源的可控性却感觉越发地低了。 18 | 19 | - 构建与部署剥离 20 | - 开发人员尽量避免直接登录服务器 21 | - 运维配置、脚本同样入库管理 22 | 23 | Next Step: Try K8s &Terraform & Rancher 24 | 25 | ![](https://camo.githubusercontent.com/36bc60e2ccb6ae78a757a5c5a7214a23514b20ff/68747470733a2f2f706172672e636f2f555a73) 26 | 27 | # 环境配置 28 | 29 | 也可以使用 Ansible 等自动化工具进行批量配置 30 | 31 | # 基础架构 32 | 33 | ## Docker 集群 34 | 35 | ## 状态(分布式数据存储)服务 36 | 37 | ```sh 38 | #!/bin/bash 39 | 40 | 41 | REDIS_CONFIG='port 6379 42 | cluster-enabled yes 43 | cluster-config-file nodes.conf 44 | cluster-node-timeout 5000 45 | appendonly yes' 46 | 47 | 48 | network=mynet 49 | 50 | 51 | docker service create --name redis \ 52 | --network $network \ 53 | --replicas=6 \ 54 | -e REDIS_CONFIG="$REDIS_CONFIG" \ 55 | -e REDIS_CONFIG_FILE="/usr/local/etc/redis/redis.conf" \ 56 | redis:3.2.6-alpine sh -c 'mkdir -p $(dirname $REDIS_CONFIG_FILE) && echo "$REDIS_CONFIG" > $REDIS_CONFIG_FILE && cat $REDIS_CONFIG_FILE && redis-server $REDIS_CONFIG_FILE' 57 | 58 | 59 | sleep 2 60 | docker service ps redis --no-trunc 61 | 62 | 63 | # run the redis-trib.rb script(the docker inspect runs on the host and the echo output is passed the along to the ruby container) 64 | docker run -it --rm --net $network ruby sh -c "\ 65 | gem install redis --version 3.2 \ 66 | && wget http://download.redis.io/redis-stable/src/redis-trib.rb \ 67 | && ruby redis-trib.rb create --replicas 1 \ 68 | \$(getent hosts tasks.redis | awk '{print \$1 \":6379\"}') " 69 | ``` 70 | 71 | ``` 72 | ./redis.sh 73 | ``` 74 | 75 | ```sh 76 | docker run -it --rm --net mynet redis:3.2.6 redis-cli -c -h redis -p 6379 77 | 78 | 10.0.0.7:6379> set mykey1 1 79 | OK 80 | 10.0.0.7:6379> set mykey2 2 81 | -> Redirected to slot [14119] located at 10.0.0.6:6379 82 | OK 83 | 10.0.0.6:6379> set mykey3 3 84 | -> Redirected to slot [9990] located at 10.0.0.4:6379 85 | OK 86 | 10.0.0.4:6379> get mykey1 87 | -> Redirected to slot [1860] located at 10.0.0.7:6379 88 | "1" 89 | 10.0.0.7:6379> get mykey2 90 | -> Redirected to slot [14119] located at 10.0.0.6:6379 91 | "2" 92 | 10.0.0.6:6379> get mykey3 93 | -> Redirected to slot [9990] located at 10.0.0.4:6379 94 | "3" 95 | 10.0.0.4:6379> 96 | ``` 97 | 98 | ## Serverless 99 | 100 | 使用 [faas](https://github.com/alexellis/faas) 工具 101 | 102 | ```sh 103 | # 抓取代码库 104 | git clone https://github.com/alexellis/faas 105 | 106 | 107 | # 执行 Stack 安装 108 | ./deploy_stack.sh 109 | 110 | 111 | # docker-composer.yml 112 | version: "3" 113 | 114 | services: 115 | 116 | 117 | 118 | # Core API services are pinned, HA is provided for functions. 119 | 120 | gateway: 121 | 122 | volumes: 123 | 124 | - "/var/run/docker.sock:/var/run/docker.sock" 125 | 126 | ports: 127 | 128 | - 8080:8080 129 | 130 | image: functions/gateway:0.6.1 131 | 132 | networks: 133 | 134 | - functions 135 | 136 | environment: 137 | 138 | dnsrr: "true" # Temporarily use dnsrr in place of VIP while issue persists on PWD 139 | 140 | deploy: 141 | 142 | placement: 143 | 144 | constraints: [node.role == manager] 145 | ``` 146 | 147 | ``` 148 | root@ubuntu-176:/tmp/faas# docker stack ps func 149 | 150 | ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 151 | 152 | ehg6btnb78g3 func_nodeinfo.1 functions/nodeinfo:latest ubuntu-177 Running Preparing 2 minutes ago 153 | 154 | od9h1nhdyejc func_alertmanager.1 functions/alertmanager:latest Ubuntu-11 Running Preparing 2 minutes ago 155 | 156 | cn43a00rhp9l func_hubstats.1 alexellis2/faas-dockerhubstats:latest Ubuntu-15 Running Running about a minute ago 157 | 158 | u59bj7p586m4 func_prometheus.1 functions/prometheus:latest Ubuntu-11 Running Preparing 2 minutes ago 159 | 160 | yt9gmyhccrcu func_echoit.1 functions/alpine:health ubuntu-178 Running Running about a minute ago 161 | 162 | vy5wsgr50z61 func_gateway.1 functions/gateway:0.6.1 Ubuntu-11 Running Preparing 2 minutes ago 163 | 164 | y42uhzjsikoo func_decodebase64.1 functions/alpine:health ubuntu-77.14 Running Running less than a second ago 165 | 166 | xen7wn6timqz func_base64.1 functions/alpine:health Ubuntu-11 Running Preparing 2 minutes ago 167 | 168 | kcz0sym9jqu6 func_webhookstash.1 functions/webhookstash:latest ubuntu-179 Running Running 24 seconds ago 169 | 170 | 3udkdbip8c4p func_wordcount.1 functions/alpine:health Ubuntu-15 Running Running 2 minutes ago 171 | 172 | st0c4ibfze83 func_markdown.1 alexellis2/faas-markdownrender:latest Ubuntu-11 Running Running about a minute ago 173 | 174 | 175 | 176 | root@ubuntu-176:/tmp/faas# docker stack services func 177 | 178 | ID NAME MODE REPLICAS IMAGE PORTS 179 | 180 | 3wniopfhdykt func_echoit replicated 1/1 functions/alpine:health 181 | 182 | 6he8cqjmb0jm func_decodebase64 replicated 1/1 functions/alpine:health 183 | 184 | d6f9xlqdbwe8 func_webhookstash replicated 1/1 functions/webhookstash:latest 185 | 186 | en5at93ev76k func_gateway replicated 1/1 functions/gateway:0.6.1 *:8080->8080/tcp 187 | 188 | nsfmi41aphbj func_base64 replicated 1/1 functions/alpine:health 189 | 190 | qngwiey9b8ek func_nodeinfo replicated 1/1 functions/nodeinfo:latest 191 | 192 | s6i49lq9epas func_prometheus replicated 0/1 functions/prometheus:latest *:9090->9090/tcp 193 | 194 | skkmxt2bx4je func_wordcount replicated 1/1 functions/alpine:health 195 | 196 | ty5rbwoozz6e func_hubstats replicated 1/1 alexellis2/faas-dockerhubstats:latest 197 | 198 | xne9dph1xcit func_alertmanager replicated 0/1 functions/alertmanager:latest *:9093->9093/tcp 199 | 200 | xvwb9uuvqemo func_markdown replicated 1/1 alexellis2/faas-markdownrender:latest 201 | ``` 202 | 203 | # 服务网关 204 | 205 | 可以参考笔者的[清新脱俗的 Web 服务器 Caddy](https://zhuanlan.zhihu.com/p/25850060) 一文。 206 | 207 | # DevOps 208 | 209 | DevOps 包含了 CI 应用服务部署与更新与 Monitor & Alert 监控与告警两部分 210 | 211 | 构建与部署剥离 212 | 213 | 使用 ELK 进行日志处理,使用 Prometheus 进行线上应用实时监控 214 | -------------------------------------------------------------------------------- /10~边缘计算/开源项目梳理.md: -------------------------------------------------------------------------------- 1 | # 边缘计算开源项目梳理 2 | 3 | # KubeEdge 4 | 5 | KubeEdge 名字来源于 Kube + Edge,是面向边缘计算场景、专为边云协同设计的业界首个云原生边缘计算框架,在 Kubernetes 原生的容器编排调度能力之上实现了边云之间的应用协同、资源协同、数据协同和设备协同等能力,完整打通了边缘计算中云、边、设备协同的场景。KubeEdge 于 2019 年 3 月正式进入 CNCF 成为沙箱级项目(Sandbox),也成为 CNCF 首个云原生边缘计算项目。并于 2020 年 9 月晋升为孵化级项目(Incubating),成为 CNCF 首个孵化的云原生边缘计算项目。 6 | 7 | KubeEdge 架构上分为云、边、端三个层次。云端负责应用和配置的校验、下发,边缘侧负责运行边缘应用和管理接入的设备,设备端运行各种边缘设备。KubeEdge 完整打通了边缘计算中云、边、设备协同的场景。 8 | 9 | ![KubeEdge 示意图](https://s3.ax1x.com/2021/01/16/sB4bD0.png) 10 | 11 | 其核心能力包括: 12 | 13 | - 支持复杂的边云网络环境:双向多路复用边云消息通道提供应用层可靠增量同步机制,支持高时延、低质量网络环境。 14 | - 应用/数据边缘自治:支持边缘离线自治及边缘数据处理工作流 15 | - 边云一体资源调度和流量协同:支持边云节点混合管理、应用流量统一调度 16 | - 支持海量边缘设备管理:资源占用业界同类最小;提供可插拔设备管理框架,支持自定义插件扩展 17 | - 开放生态:100% 兼容 Kubernetes 原生能力;支持 MQTT、Modbus、Bluetooth、Wifi、ZigBee 等业界主流设备通信协议。 18 | 19 | # Starlingx 20 | 21 | StarlingX,一个专注于对低延迟和高性能应用进行优化的开源边缘计算及物联网云平台,StarlingX 项目旨在为边缘计算重新配置经过验证的云技术,在大规模分布式计算环境中提供成熟且稳健的云平台。StarlingX 是适用于裸机、虚拟机和容器化部署环境的完整边缘云基础设施平台,适用于对高可用性(HA)、服务质量(QoS)、性能和低延迟等有严格要求的应用场景。 22 | 23 | ![StarlingX 架构图](https://s3.ax1x.com/2021/01/17/ssOaDS.png) 24 | 25 | StarlingX 提供了可部署、可扩展和高度可靠的边缘基础设施软件平台,用以构建关键任务边缘云。作为完整的堆栈进行测试和发布,StarlingX 利用了其他开源项目的组件,例如 Ceph、Linux、KVM、OpenStack 和 Kubernetes,并通过配置和故障管理等新服务对其进行了补充。StarlingX 社区针对安全性,超低延迟,超高服务正常运行时间以及面向边缘和 IoT 用例的简化操作优化了解决方案。 26 | 27 | StarlingX 的应用场景包括远端或最后一英里,以及应用用例,例如工厂中的内部云、工业物联网、自动驾驶汽车和其他基于运输的物联网应用,多访问边缘计算(MEC)和虚拟无线电接入网络(vRAN)、5G、智能建筑和城市、增强和虚拟现实、高清媒体内容交付、监控、医疗影像以及通用客户前提设备(uCPE),在今后的几年中将会呈现高速的增长。 28 | 29 | # K3s 30 | 31 | k3s 是首个进入 CNCF 沙箱项目的 K8S 发行版,同时也是当前全球用户量最大的 CNCF 认证轻量级 K8S 发行版。自 2019 年 3 月发布以来,备受全球开发者们关注,至今 GitHub Star 数已超过 15,500,成为了开源社区最受欢迎的边缘计算 K8S 解决方案。截至目前,K3s 全球下载量超过 100 万次,每周平均被安装超过 2 万次,其中 30%的下载量来自中国。 32 | 33 | k3s 专为在资源有限的环境中运行 Kubernetes 的研发和运维人员设计,将满足日益增长的在边缘计算环境中运行在 x86、ARM64 和 ARMv7 处理器上的小型、易于管理的 Kubernetes 集群需求。k3s 的发布,为开发者们提供了以“Rancher 2.X + k3s”为核心的从数据中心到云到边到端的 K8S 即服务(Kubernetes-as-a-Service),推动 KubernetesEverywhere。 34 | 35 | 当组织同时使用 Rancher 和 K3s 时,组织将拥有一个在边缘运行 Kubernetes 的简单且完整的解决方案。 36 | 37 | ![K3s 加固](https://s3.ax1x.com/2021/01/17/ssOy3q.png) 38 | 39 | 通过消除安装 Kubernetes 的复杂性和学习成本,K3s 极大地简化了边缘部署。K3s 与架构无关,并且占用空间极小。K3s 的易用性使组织可以从堆栈中获得更高的价值,将集群部署至数百甚至数千个地点,并快速启动这些集群。Rancher 通过 Rancher 持续交付(Continuous Delivery)帮助 K3s 用户管理大量集群,Rancher 为用户提供了一个控制器,使他们能够在边缘高效地管理 Kubernetes。K3s 为边缘部署提供动力,并推动组织实现真正的数字化转型。 40 | 41 | # EdgeX Foundry 42 | 43 | EdgeX Foundry 是由 Linux 基金会运营的厂商中立的开放源码项目,旨在为物联网边缘计算创建公共开放的框架。该项目的核心是基于与硬件和操作系统完全无关的参考软件平台建立的互操作框架,使能即插即用的组件生态系统,统一市场,加速物联网方案的部署。 44 | 45 | ![EdgeX Foundry](https://s3.ax1x.com/2021/01/17/ssOWbF.png) 46 | 47 | EdgeX Foundry 使有意参与的各方在开放与互操作的物联网方案中自由协作,无论他们是使用公开标准或私有方案。Edgex Foundry 由 LF Edge 运营,目前有 60 多家全球企业参与,150 多名工程师贡献代码。EdgeX 中国项目于 2019 年底成立,负责运营中国区的技术推广并吸引更多的代码贡献。VMware 和 Intel 是 EdgeX 中国项目的联合维护单位。 48 | 49 | # EdgeGallery 50 | 51 | EdgeGallery 是由设备厂商、运营商,垂直行业伙伴等联合发起的一个 5G 边缘计算开源项目。目的是打造一个符合 5G MEC“联接+计算”特点的边缘计算公共平台,实现网络能力(尤其是 5G 网络)开放的标准化和 MEC 应用开发、测试、迁移和运行等生命周期流程的通用化。EdgeGallery 不仅是一个 MEP 平台,未来更是一个面向应用和开发者的端到端解决方案,将为应用开发者、边缘运营及运维人员提供一站式服务。 52 | 53 | ![EdgeGallery](https://s3.ax1x.com/2021/01/17/ssXlrT.png) 54 | 55 | EdgeGallery 针对边缘计算场景提供了设计、分发以及运行的 E2E 平台以及工具链。为了繁荣边缘计算生态,EdgeGallery 还不断丰富样例应用、联邦商城、5G 实验室等: 56 | 57 | - E2E 应用开发部署平台:a) API 能力中心:面向 APP 开发者,提供更加丰富的开放能力 b) 开发调测换环境:提供 5G MEC 开发调测沙箱以及外场环境 c) 5G 网络集成:UPF 对接、提供 DNS、流量等规则配置能力 58 | - APP Store 联邦以及 APP 生态共建:a) 3rd APP Store 管理:提供三方 APP 仓库的注册管理功能 b) APP Store 分布式联邦:实现与三方 APP 仓库的 APP 推送共享 c) APP 身份证:提供 APP 认证平台,运营商可定制测试用例 59 | - 边缘节点可视化管理:a) 边缘节点 GIS 管理:提供基于在线地图的边缘节点以及资源管理,提供街道级别的 MEP 详细拓扑 b) 5G UPF 分流规则配置:UPF 流量转发规则,DNS 规则的可视化配置 60 | - 边缘应用与服务可视化:a) 本地 Portal:新增 MEP 单节点管理界面,用以管理应用和服务,实现基本的服务治理可视化能力 b) 节点服务治理:提供单节点服务的 health check,多节点服务隔离,5G 网络集成等能力在未来的版本中,社区计划持续提升 EdgeGallery 平台的用户体验、稳定性、安全性以及提供更丰富的开放能力。 61 | 62 | # Akraino 63 | 64 | Akraino 是针对 Edge 的一组开放式基础架构和应用蓝图,涵盖了广泛的用例,包括针对提供商和企业边缘域的 5G,AI,Edge IaaS / PaaS,IoT。这些蓝图是由 Akraino 社区创建的,专门针对各种形式的边缘。所有这些蓝图的统一之处在于它们已经由社区进行了测试,可以按原样使用,或者用作自定义新边缘蓝图的起点。 65 | 66 | ![Akraino](https://s3.ax1x.com/2021/01/17/ssXcid.png) 67 | 68 | # Baetyl 69 | 70 | Baetyl,原名 OpenEdge,最早是由百度智能云打造的边缘计算平台,于 2018 年 12 月 6 日正式对外开源,也是是中国首个全面开源的边缘计算平台。在 2019 年 9 月 23 日,百度宣布将 BAETYL 捐赠给 Linux 基金会旗下社区,是中国首个 LF Edge 捐赠项目。2020-07-08,Baetyl 2.0 正式发布,同步开源了边缘计算云管平台 Baetyl-Cloud。 71 | 72 | Baetyl 旨在将云计算能力拓展至用户现场。提供临时离线、低延时的计算服务,包括数据接入、消息路由、函数计算、流式计算、AI 推断等功能。配合最新开源的云管平台 Baetyl-Cloud,可以实现应用部署、配置下发、系统监控等功能。提供了完整的“云管理、边运行”的一体化解决方案。 73 | 74 | ![Baetyl](https://s3.ax1x.com/2021/01/17/ssXodg.md.png) 75 | 76 | 云端管理套件 77 | 78 | 云端管理套件(Cloud Management Suite)负责管理所有资源,包括节点、应用、配置、部署等。所有功能的实现都插件化,方便功能扩展和第三方服务的接入,提供丰富的应用。云端管理套件的部署非常灵活,即可部署在公有云上,又可部署在私有化环境中,还可部署在普通设备上,支持 K8S/K3S 部署,支持单租户和多租户。 79 | 80 | 开源版云端管理套件提供的基础功能如下: 81 | 82 | - 边缘节点管理 83 | - 在线安装 84 | - 端云同步(影子) 85 | - 节点信息 86 | - 节点状态 87 | - 应用状态 88 | - 应用部署管理 89 | - 容器应用 90 | - 函数应用 91 | - 节点匹配(自动) 92 | - 配置管理 93 | - 普通配置 94 | - 函数配置 95 | - 密文 96 | - 证书 97 | - 镜像库凭证 98 | 99 | 开源版本包含上述所有功能的 RESTful API,暂不包含前端界面(Dashboard)。边缘计算框架(Edge Computing Framework)运行在边缘节点的 Kubernetes 上,管理和部署节点的所有应用,通过应用服务提供各式各样的能力。应用包含系统应用和普通应用,系统应用全部由 Baetyl 官方提供,用户无需配置。目前有如下几个系统应用: 100 | 101 | - baetyl-init:负责激活边缘节点到云端,并初始化 baetyl-core,任务完成后就会退出。 102 | - baetyl-core:负责本地节点管理(node)、端云数据同步(sync)和应用部署(engine)。 103 | - baetyl-function: 所有函数运行时服务的代理模块,函数调用都到通过这个模块。 104 | 105 | 目前框架支持 Linux/amd64、Linux/arm64、Linux/armv,如果边缘节点的资源有限,可考虑使用轻量版 Kubernetes:K3S。 106 | 107 | # OpenYurt 108 | 109 | OpenYurt 是业界首个开源的非侵入式边缘计算云原生平台,秉承“Extending your native Kubernetes to Edge”的非侵入式设计理念,拥有可实现边缘计算全场景覆盖的能力。使用 OpenYurt(Yurt,/jɜːrt/,蒙古包)作为开源项目名称,期望以其“形”来表示边缘计算侧重于创建一个集中管理但物理分布的基础设施,并支持自动/自治运行操作的含义。OpenYurt 主打“非侵入式云边一体化”概念,依托原生 Kubernetes 强大的容器编排、调度能力,通过众多边缘计算应用场景锤炼,实现了一整套对原生 Kubernetes“零”侵入的边缘云原生方案,提供诸如边缘自治、高效运维通道、边缘单元化管理、边缘流量拓扑管理,安全容器、边缘 Serverless/FaaS、异构资源支持等能力。OpenYurt 能帮用户解决在海量边、端资源上完成大规模应用交付、运维、管控的问题,并提供中心服务下沉通道,实现和边缘计算应用的无缝对接。 110 | 111 | ![OpenYurt 架构](https://s3.ax1x.com/2021/01/17/ssxVaD.png) 112 | 113 | 在短短一年内,作为公共云服务 ACK@Edge 的核心框架,OpenYurt 已实现全网覆盖和本地覆盖的全场景落地,全网覆盖的应用场景如 CDN、音视频直播、物联网、物流、工业大脑、城市大脑等;本地覆盖的应用场景和案例如阿里云 LinkEdge、优酷、盒马、AIBox、银泰商城等。 114 | 115 | # SuperEdge 116 | 117 | 2020 年 12 月 19 日,SuperEdge 项目由腾讯、Intel、VMware、虎牙直播、寒武纪、首都在线和美团联合宣布开源,在 github 上发布首个版本。SuperEdge 是 Kubernetes 原生的边缘容器方案,它将 Kubernetes 强大的容器管理能力扩展到边缘计算场景中,针对边缘计算场景中常见的技术挑战提供了解决方案,如:单集群节点跨地域、云边网络不可靠、边缘节点位于 NAT 网络等。这些能力可以让应用很容易地部署到边缘计算节点上,并且可靠地运行。 118 | 119 | SuperEdge 支持所有 Kubernetes 资源类型、API 接口、使用方式、运维工具,无额外的学习成本。也兼容其他云原生项目,如:Promethues,使用者可以结合其他所需的云原生项目一起使用。SuperEdge 拥有如下特性: 120 | 121 | - Kubernetes 原生:SuperEdge 基于 Kubernetes 强大的容器编排、调度能力加强构建,其易于集成的特性使开发者无需对 Kubernetes 进行复杂的结构性修改即可轻松部署,SuperEdge 还能完全兼容 Kubernetes 的所有原生 API 及其他资源。 122 | - 边缘自治:SuperEdge 能够实现节点级边缘自治,当边缘节点处于离线状态或者与云端网络连接不稳定时,边缘节点和网络依然可以自主运行和工作,这能够有效化解网络不可靠所带来的不利影响。 123 | - 分布式节点健康监测:SuperEdge 是业内首个将健康监测带到边缘侧的开源容器管理系统,这意味着 SuperEdge 能够在边缘侧持续守护进程,并收集节点的故障信息,实现更加快速和精准的问题发现与报告。此外,其分布式的设计还可以实现多区域、多范围的监测和管理。 124 | - 内置服务网格框架:SuperEdge 能够自动部署多区域的微服务,方便管理在服务器上运行的数量庞大的微服务,从而减少管理和编程成本。同时,网格内闭环服务可以有效减少运行负载,提高系统的容错能力和可用性。 125 | - 内网穿透:SuperEdge 能够保证 Kubernetes 节点在有无公共网络的情况下都可以连续运行和维护,并且同时支持传输控制协议(TCP)、超文本传输协议(HTTP)和超文本传输安全协议(HTTPS)。 126 | 127 | # Azure IoT Edge 128 | 129 | Azure IoT Edge 是基于物联网 (IoT) 中心构建的 IoT 服务。此服务供想要在设备上(也称为“在边缘上”)而不是在云中分析数据的客户使用。通过将部分工作负荷移至边缘,设备将消息发送到云所花费的时间可以更少,并且设备可以对状态更改更快地做出响应。Azure IoT Edge 主要将基于云的分析和定制的业务逻辑转移到边缘设备,使企业能够专注于洞察商业机会而非数据管理。微软表示,这些设备现在将能够立即采取实时数据行动。借助开源的 Azure IoT Edge,开发人员可以更灵活地控制自己的边缘解决方案,以及运行时或调试问题。 130 | 131 | 为了解决 Azure IoT Edge 大规模部署的安全问题,Azure IoT Edge 深入集成了设备调配服务,以安全地配置数以万计的设备和 Azure IoT Edge 安全管理员,这些管理员可以用来保护边缘设备及其组件。自动设备管理(ADM)可以基于设备元数据将大型物联网边缘模块部署到设备。 132 | 133 | Azure IoT Edge 支持 C#,C,Node.js,Python 和 Java 等编程语言。它还提供 VSCode 模块开发,测试和部署工具,以及带 VSTS 的 CI/CD 管道。部署 Azure IoT Edge 有三个必要组件,即 Azure IoT Edge Runtime,Azure IoT Hub 和 Edge 模块。Azure IoT Edge Runtime 是免费且开源的,但客户必须使用付费的 Azure IoT Hub 实例进行扩展。边缘设备的管理和部署也将基于 Azure 服务或客户使用的 Edge 模块。 134 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/AUFS.md: -------------------------------------------------------------------------------- 1 | # AUFS 2 | 3 | 联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into asingle virtual filesystem)。联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。 4 | 5 | Docker 支持的 UnionFS 包括 OverlayFS,AUFS,devicemapper,vfs 以及 btrfs 等,查看 UnionFS 版本可以用 docker info 查看对应输出中的 Storage 项即可,早期的 Docker 版本用 AUFS 和 devicemapper 居多,新版本 Docker 在 Linux 3.18 之后版本基本默认用 OverlayFS。 6 | 7 | # OverlayFS 8 | 9 | OverlayFS 与早期用过的 AUFS 类似,不过它比 AUFS 更简单,读写性能更好,在 docker-ce18.03 版本中默认用的存储驱动是 overlay2,老版本 overlay 官方已经不推荐使用。它将两个目录 upperdir 和 lowdir 联合挂载到一个 merged 目录,提供统一视图。其中 upperdir 是可读写层,对容器修改写入在该目录中,它也会隐藏 lowerdir 中相同的文件。而 lowdir 是只读层,Docker 镜像在这层。 10 | 11 | ![OverlayFS](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424144518.png) 12 | 13 | 在看 Docker 镜像和容器存储结构前,可以先简单操作下 OverlayFS 看下基本概念。创建了 lowerdir 和 upperdir 两个目录,然后用 overlayfs 挂载到 merged 目录,这样在 merged 目录可以看到两个目录的所有文件 both.txt 和 only.txt。其中 upperdir 是可读写的,而 lowerdir 只读。通过 merged 目录来操作文件可以发现: 14 | 15 | - 读取文件时,如果 upperdir 不存在该文件,则会从 lowerdir 直接读取。 16 | 17 | - 修改文件时并不影响 lowerdir 中的文件,因为它是只读的。 18 | 19 | - 如果修改的文件在 upperdir 不存在,则会从 lowerdir 拷贝到 upperdir,然后在 upperdir 里面修改该文件,并不影响 lowerdir 目录的文件。 20 | 21 | - 删除文件则是将 upperdir 中将对应文件设置成了 c 类型,即字符设备类型来隐藏已经删除的文件(与 AUFS 创建一个 whiteout 文件略有不同)。 22 | 23 | ```s 24 | root@host:/home/vagrant/overlaytest# tree -a 25 | . 26 | |-- lowerdir 27 | | |-- both.txt 28 | | `-- only.txt 29 | |-- merged 30 | |-- upperdir 31 | | `-- both.txt 32 | `-- workdir 33 | `-- work 34 | 35 | 5 directories, 3 files 36 | root@host:/home/vagrant/overlaytest# mount -t overlay overlay -olowerdir=./lowerdir,upperdir=./upperdir,workdir=./workdir ./merged 37 | root@host:/home/vagrant/overlaytest# tree 38 | . 39 | |-- lowerdir 40 | | |-- both.txt 41 | | `-- only.txt 42 | |-- merged 43 | | |-- both.txt 44 | | `-- only.txt 45 | |-- upperdir 46 | | `-- both.txt 47 | `-- workdir 48 | `-- work 49 | 50 | 5 directories, 5 files 51 | root@host:/home/vagrant/overlaytest# tree -a 52 | . 53 | |-- lowerdir 54 | | |-- both.txt 55 | | `-- only.txt 56 | |-- merged 57 | | |-- both.txt 58 | | `-- only.txt 59 | |-- upperdir 60 | | `-- both.txt 61 | `-- workdir 62 | `-- work 63 | 64 | 5 directories, 5 files 65 | root@host:/home/vagrant/overlaytest# echo "modified both" > merged/both.txt 66 | root@host:/home/vagrant/overlaytest# cat upperdir/both.txt 67 | modified both 68 | root@host:/home/vagrant/overlaytest# cat lowerdir/both.txt 69 | lower both.txt 70 | root@host:/home/vagrant/overlaytest# echo "modified only" > merged/only.txt 71 | root@host:/home/vagrant/overlaytest# tree 72 | . 73 | |-- lowerdir 74 | | |-- both.txt 75 | | `-- only.txt 76 | |-- merged 77 | | |-- both.txt 78 | | `-- only.txt 79 | |-- upperdir 80 | | |-- both.txt 81 | | `-- only.txt 82 | `-- workdir 83 | `-- work 84 | 85 | 5 directories, 6 files 86 | root@host:/home/vagrant/overlaytest# cat upperdir/only.txt 87 | modified only 88 | root@host:/home/vagrant/overlaytest# cat lowerdir/only.txt 89 | lower only.txt 90 | root@host:/home/vagrant/overlaytest# tree -a 91 | . 92 | |-- lowerdir 93 | | |-- both.txt 94 | | `-- only.txt 95 | |-- merged 96 | | |-- both.txt 97 | | `-- only.txt 98 | |-- upperdir 99 | | |-- both.txt 100 | | `-- only.txt 101 | `-- workdir 102 | `-- work 103 | 104 | 5 directories, 6 files 105 | root@host:/home/vagrant/overlaytest# rm merged/both.txt 106 | root@host:/home/vagrant/overlaytest# tree -a 107 | . 108 | |-- lowerdir 109 | | |-- both.txt 110 | | `-- only.txt 111 | |-- merged 112 | | `-- only.txt 113 | |-- upperdir 114 | | |-- both.txt 115 | | `-- only.txt 116 | `-- workdir 117 | `-- work 118 | root@host:/home/vagrant/overlaytest# ls -ls upperdir/both.txt 119 | 0 c--------- 1 root root 0, 0 May 19 02:31 upperdir/both.txt 120 | ``` 121 | 122 | 回到 Docker 里面,我们拉取一个 Nginx 镜像,有三层镜像,可以看到在 overlay2 对应每一层都有个目录(注意,这个目录名跟镜像层名从 docker1.10 版本后名字已经不对应了),另外的 l 目录是指向镜像层的软链接。最底层存储的是基础镜像 debian/alpine,上一层是安装了 nginx 增加的可执行文件和配置文件,而最上层是链接/dev/stdout 到 nginx 日志文件。而每个子目录下面的 diff 目录用于存储镜像内容,work 目录是 OverlayFS 内部使用的,而 link 文件存储的是该镜像层对应的短名称,lower 文件存储的是下一层的短名称。 123 | 124 | ```s 125 | root@stretch:/home/vagrant# docker pull nginx 126 | Using default tag: latest 127 | latest: Pulling from library/nginx 128 | f2aa67a397c4: Pull complete 129 | 3c091c23e29d: Pull complete 130 | 4a99993b8636: Pull complete 131 | Digest: sha256:0fb320e2a1b1620b4905facb3447e3d84ad36da0b2c8aa8fe3a5a81d1187b884 132 | Status: Downloaded newer image for nginx:latest 133 | 134 | root@stretch:/home/vagrant# ls -ls /var/lib/docker/overlay2/ 135 | total 16 136 | 4 drwx------ 4 root root 4096 May 19 04:17 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0 137 | 4 drwx------ 3 root root 4096 May 19 04:17 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554 138 | 4 drwx------ 4 root root 4096 May 19 04:17 f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe 139 | 4 drwx------ 2 root root 4096 May 19 04:17 l 140 | 141 | root@stretch:/var/lib/docker/overlay2# ls 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0/ 142 | diff link lower work 143 | ``` 144 | 145 | 从我们示例可以看到,三层中 f311 是最顶层,下面分别是 0949 和 8af9 这两层。 146 | 147 | ```s 148 | root@stretch:/var/lib/docker/overlay2# cat f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe/lower 149 | l/7B2WM6DC226TCJU6QHJ4ABKRI6:l/4FHO2G5SWWRIX44IFDHU62Z7X2 150 | root@stretch:/var/lib/docker/overlay2# cat 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0/lower 151 | l/4FHO2G5SWWRIX44IFDHU62Z7X2 152 | root@stretch:/var/lib/docker/overlay2# cat 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554/link 153 | 4FHO2G5SWWRIX44IFDHU62Z7X2 154 | ``` 155 | 156 | 此时我们启动一个 Nginx 容器,可以看到 overlay2 目录多了两个目录,多出来的就是容器层的目录和只读的容器 init 层。容器目录下面的 merged 就是我们前面提到的联合挂载目录了,而 lowdir 则是它下层目录。而容器 init 层用来存储与这个容器内环境相关的内容,如 /etc/hosts 和 /etc/resolv.conf 文件,它居于其他镜像层之上,容器层之下。 157 | 158 | ```s 159 | root@stretch:/var/lib/docker/overlay2# docker run -idt --name nginx nginx 160 | 01a873eeba41f00a5a3deb083adf5ed892c55b4680fbc2f1880e282195d3087b 161 | 162 | root@stretch:/var/lib/docker/overlay2# ls -ls 163 | 4 drwx------ 4 root root 4096 May 19 04:17 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0 164 | 4 drwx------ 5 root root 4096 May 19 09:11 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1 165 | 4 drwx------ 4 root root 4096 May 19 09:11 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1-init 166 | 4 drwx------ 3 root root 4096 May 19 04:17 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554 167 | 4 drwx------ 4 root root 4096 May 19 04:17 f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe 168 | 4 drwx------ 2 root root 4096 May 19 09:11 l 169 | 170 | root@stretch:/home/vagrant# ls -ls /var/lib/docker/overlay2/11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/ 171 | 4 drwxr-xr-x 4 root root 4096 May 19 09:11 diff 172 | 4 -rw-r--r-- 1 root root 26 May 19 09:11 link 173 | 4 -rw-r--r-- 1 root root 115 May 19 09:11 lower 174 | 4 drwxr-xr-x 1 root root 4096 May 19 09:11 merged 175 | 4 drwx------ 3 root root 4096 May 19 09:11 work 176 | 177 | root@stretch:/var/lib/docker/overlay2# ls 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/merged/ 178 | bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var 179 | 180 | root@stretch:/var/lib/docker/overlay2# ls 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/diff/ 181 | run var 182 | ``` 183 | 184 | 如果我们在容器中修改文件,则会反映到容器层的 merged 目录相关文件,容器层的 diff 目录相当于 upperdir,其他层是 lowerdir。如果之前容器层 diff 目录不存在该文件,则会拷贝该文件到 diff 目录并修改。读取文件时,如果 upperdir 目录找不到,则会直接从下层的镜像层中读取。 185 | 186 | # 写时复制 187 | 188 | 容器运行时的只读模板。每一个镜像由一系列的层 (layers) 组成,层是由 Dockerfile 指定。copy on write 写时复制。容器是由镜像所创建,会根据多层文件系统构建一个镜像栈,只有栈的最顶层是读写层。如果发生对只读层的写操作时会将该文件复制到读写层,并隐藏只读层的文件。 189 | 190 | # Links 191 | 192 | - https://jvns.ca/blog/2019/11/18/how-containers-work--overlayfs/ How containers work: overlayfs 193 | -------------------------------------------------------------------------------- /01~虚拟化/Linux 资源隔离/Namespaces.md: -------------------------------------------------------------------------------- 1 | # Namespaces 2 | 3 | 简单的讲就是,Linux Namespace 允许用户在独立进程之间隔离 CPU 等资源。进程的访问权限及可见性仅限于其所在的 Namespaces。因此,用户无需担心在一个 Namespace 内运行的进程与在另一个 Namespace 内运行的进程冲突。甚至可以同一台机器上的不同容器中运行具有相同 PID 的进程。同样的,两个不同容器中的应用程序可以使用相同的端口。 4 | 5 | ![Linux Container Namespace](https://tva1.sinaimg.cn/large/007rAy9hgy1g2zdhwngx6j30u00m0wgg.jpg) 6 | 7 | Namespaces 用于环境隔离,Linux kernel 支持的 Namespace 包括 UTS, IPC, PID, NET, NS, USER 以及新加入的 CGROUP 等,UTS 用于隔离主机名和域名,使用标识 CLONE_NEWUTS,IPC 用于隔离进程间通信资源如消息队列等,使用标识 CLONE_NEWIPC,PID 隔离进程,NET 用于隔离网络,NS 用于隔离挂载点,USER 用于隔离用户组。默认情况下,通过 clone 系统调用创建子进程的 namespace 与父进程是一致的,而你可以在 clone 系统调用中通过 flag 参数设置隔离的名字空间来隔离,当然也可以更加方便的直接用 unshare 命令来创建新的 namespace。查看一个进程的各 Namespace 命令如下: 8 | 9 | ```sh 10 | root@host:/home/vagrant# ls -ls /proc/self/ns/ 11 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 cgroup -> cgroup:[4026531835] 12 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 ipc -> ipc:[4026531839] 13 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 mnt -> mnt:[4026531840] 14 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 net -> net:[4026531957] 15 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 pid -> pid:[4026531836] 16 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 user -> user:[4026531837] 17 | 0 lrwxrwxrwx 1 root root 0 May 17 22:04 uts -> uts:[4026531838] 18 | ``` 19 | 20 | # PID Namespace 21 | 22 | 在容器中,有自己的 Pid namespace,因此我们看到的只有 PID 为 1 的初始进程以及它的子进程,而宿主机的其他进程容器内是看不到的。通常来说,Linux 启动后它会先启动一个 PID 为 1 的进程,这是系统进程树的根进程,根进程会接着创建子进程来初始化系统服务。PID namespace 允许在新的 namespace 创建一棵新的进程树,它可以有自己的 PID 为 1 的进程。在 PID namespace 的隔离下,子进程名字空间无法知道父进程名字空间的进程,如在 Docker 容器中无法看到宿主机的进程,而父进程名字空间可以看到子进程名字空间的所有进程。如图所示: 23 | 24 | ![Pid Namespace](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424143451.png) 25 | 26 | Linux 内核加入 PID Namespace 后,对 pid 结构进行了修改,新增的 upid 结构用于跟踪 namespace 和 pid。 27 | 28 | ```c 29 | ## 加入PID Namespace之前的pid结构 30 | struct pid { 31 | atomic_t count; /* reference counter */ 32 | int nr; /* the pid value */ 33 | struct hlist_node pid_chain; /* hash chain */ 34 | ... 35 | }; 36 | 37 | ## 加入PID Namespace之后的pid结构 38 | struct upid { 39 | int nr; /* moved from struct pid */ 40 | struct pid_namespace *ns; 41 | struct hlist_node pid_chain; /* moved from struct pid */ 42 | }; 43 | 44 | struct pid { 45 | ... 46 | int level; /* the number of upids */ 47 | struct upid numbers[0]; 48 | }; 49 | ``` 50 | 51 | 可以通过 unshare 测试下 PID namespace,可以看到新的 bash 进程它的 pid namespace 与父进程的不同了,而且它的 pid 是 1。 52 | 53 | ```sh 54 | root@host:/home/vagrant# unshare --fork --pid bash 55 | root@host:/home/vagrant# echo $$ 56 | 1 57 | root@host:/home/vagrant# ls -ls /proc/self/ns/ 58 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 cgroup -> cgroup:[4026531835] 59 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 ipc -> ipc:[4026531839] 60 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 mnt -> mnt:[4026531840] 61 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 net -> net:[4026531957] 62 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 pid -> pid:[4026532232] 63 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 user -> user:[4026531837] 64 | 0 lrwxrwxrwx 1 root root 0 May 19 15:24 uts -> uts:[4026531838] 65 | ``` 66 | 67 | ## 僵尸进程 68 | 69 | 容器的本质实际上是一个进程,是一个视图被隔离,资源受限的进程。容器里面 PID=1 的进程就是应用本身,这意味着管理虚拟机等于管理基础设施,因为我们是在管理机器,但管理容器却等于直接管理应用本身。这也是之前说过的不可变基础设施的一个最佳体现,这个时候,你的应用就等于你的基础设施,它一定是不可变的。 70 | 71 | 在容器中,1 号进程一般是 entry point 进程,针对上面这种将孤儿进程的父进程置为 1 号进程进而避免僵尸进程处理方式,容器是处理不了的。进而就会导致容器中在孤儿进程这种异常场景下僵尸进程无法彻底处理的窘境。所以说,容器的单进程模型的本质其实是容器中的 1 号进程并不具有管理多进程、多线程等复杂场景下的能力。如果一定在容器中处理这些复杂情况的,那么需要开发者对 entry point 进程赋予这种能力。这无疑是加重了开发者的心智负担,这是任何一项大众技术或者平台框架都不愿看到的尴尬之地。 72 | 73 | 例子里面有一个程序叫做 Helloworld,这个 Helloworld 程序实际上是由一组进程组成的,需要注意一下,这里说的进程实际上等同于 Linux 中的线程。因为 Linux 中的线程是轻量级进程,所以如果从 Linux 系统中去查看 Helloworld 中的 pstree,将会看到这个 Helloworld 实际上是由四个线程组成的,分别是 {api、main、log、compute}。也就是说,四个这样的线程共同协作,共享 Helloworld 程序的资源,组成了 Helloworld 程序的真实工作情况。这是操作系统里面进程组或者线程组中一个非常真实的例子,以上就是进程组的一个概念。 74 | 75 | Helloworld 程序由四个进程组成,这些进程之间会共享一些资源和文件。那么现在有一个问题:假如说现在把 Helloworld 程序用容器跑起来,你会怎么去做? 76 | 77 | 当然,最自然的一个解法就是,我现在就启动一个 Docker 容器,里面运行四个进程。可是这样会有一个问题,这种情况下容器里面 PID=1 的进程该是谁? 比如说,它应该是我的 main 进程,那么问题来了,“谁”又负责去管理剩余的 3 个进程呢? 78 | 79 | 这个核心问题在于,容器的设计本身是一种“单进程”模型,不是说容器里只能起一个进程,由于容器的应用等于进程,所以只能去管理 PID=1 的这个进程,其他再起来的进程其实是一个托管状态。所以说服务应用进程本身就具有“进程管理”的能力。 80 | 81 | 比如说 Helloworld 的程序有 system 的能力,或者直接把容器里 PID=1 的进程直接改成 systemd,否则这个应用,或者是容器是没有办法去管理很多个进程的。因为 PID=1 进程是应用本身,如果现在把这个 PID=1 的进程给 kill 了,或者它自己运行过程中死掉了,那么剩下三个进程的资源就没有人回收了,这个是非常严重的一个问题。 82 | 83 | 反过来,如果真的把这个应用本身改成了 systemd,或者在容器里面运行了一个 systemd,将会导致另外一个问题:使得管理容器不再是管理应用本身了,而等于是管理 systemd,这里的问题就非常明显了。比如说我这个容器里面 run 的程序或者进程是 systemd,那么接下来,这个应用是不是退出了?是不是 fail 了?是不是出现异常失败了?实际上是没办法直接知道的,因为容器管理的是 systemd。这就是为什么在容器里面运行一个复杂程序往往比较困难的一个原因。 84 | 85 | 这里再帮大家梳理一下:由于容器实际上是一个“单进程”模型,所以如果你在容器里启动多个进程,只有一个可以作为 PID=1 的进程,而这时候,如果这个 PID=1 的进程挂了,或者说失败退出了,那么其他三个进程就会自然而然的成为孤儿,没有人能够管理它们,没有人能够回收它们的资源,这是一个非常不好的情况。 86 | 87 | 注意:Linux 容器的“单进程”模型,指的是容器的生命周期等同于 PID=1 的进程(容器应用进程)的生命周期,而不是说容器里不能创建多进程。当然,一般情况下,容器应用进程并不具备进程管理能力,所以你通过 exec 或者 ssh 在容器里创建的其他进程,一旦异常退出(比如 ssh 终止)是很容易变成孤儿进程的。 88 | 89 | 反过来,其实可以在容器里面 run 一个 systemd,用它来管理其他所有的进程。这样会产生第二个问题:实际上没办法直接管理我的应用了,因为我的应用被 systemd 给接管了,那么这个时候应用状态的生命周期就不等于容器生命周期。这个管理模型实际上是非常非常复杂的。 90 | 91 | # NS Namespace 92 | 93 | NS Namespace 用于隔离挂载点,不同 NS Namespace 的挂载点互不影响。创建一个新的 Mount Namespace 效果有点类似 chroot,不过它隔离的比 chroot 更加完全。这是历史上的第一个 Linux Namespace,由此得到了 NS 这个名字而不是用的 Mount。 94 | 95 | 在最初的 NS Namespace 版本中,挂载点是完全隔离的。初始状态下,子进程看到的挂载点与父进程是一样的。在新的 Namespace 中,子进程可以随意 mount/umount 任何目录,而不会影响到父 Namespace。使用 NS Namespace 完全隔离挂载点初衷很好,但是也带来了某些情况下不方便,比如我们新加了一块磁盘,如果完全隔离则需要在所有的 Namespace 中都挂载一遍。为此,Linux 在 2.6.15 版本中加入了一个 shared subtree 特性,通过指定 Propagation 来确定挂载事件如何传播。比如通过指定 MS_SHARED 来允许在一个 peer group(子 namespace 和父 namespace 就属于同一个组)共享挂载点,mount/umount 事件会传播到 peer group 成员中。使用 MS_PRIVATE 不共享挂载点和传播挂载事件。其他还有 MS_SLAVE 和 NS_UNBINDABLE 等选项。可以通过查看 cat /proc/self/mountinfo 来看挂载点信息,若没有传播参数则为 MS_PRIVATE 的选项。 96 | 97 | ![Mount Namespace](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424143519.png) 98 | 99 | 例如你在初始 namespace 有两个挂载点,通过 mount --make-shared /dev/sda1 /mntS 设置/mntS 为 shared 类型,mount --make-private /dev/sda1 /mntP 设置/mntP 为 private 类型。当你使用 unshare -m bash 新建一个 namespace 并在它们下面挂载子目录时,可以发现/mntS 下面的子目录 mount/umount 事件会传播到父 namespace,而/mntP 则不会。 100 | 101 | 在前面例子 Pid namespace 隔离后,我们在新的名字空间执行 ps -ef 可以看到宿主机进程,这是因为 ps 命令是从 /proc 文件系统读取的数据,而文件系统我们还没有隔离,为此,我们需要在新的 NS Namespace 重新挂载 proc 文件系统来模拟类似 Docker 容器的功能。 102 | 103 | ``` 104 | root@host:/home/vagrant# unshare --pid --fork --mount-proc bash 105 | root@host:/home/vagrant# ps -ef 106 | UID PID PPID C STIME TTY TIME CMD 107 | root 1 0 0 15:36 pts/1 00:00:00 bash 108 | root 2 1 0 15:36 pts/1 00:00:00 ps -ef 109 | ``` 110 | 111 | 可以看到,隔离了 NS namespace 并重新挂载了 proc 后,ps 命令只能看到 2 个进程了,跟我们在 Docker 容器中看到的一致。 112 | 113 | # NET Namespace 114 | 115 | Docker 容器中另一个重要特性是网络独立(之所以不用隔离一词是因为容器的网络还是要借助宿主机的网络来通信的),使用到 Linux 的 NET Namespace 以及 vet。veth 主要的目的是为了跨 NET namespace 之间提供一种类似于 Linux 进程间通信的技术,所以 veth 总是成对出现,如下面的 veth0 和 veth1。它们位于不同的 NET namespace 中,在 veth 设备任意一端接收到的数据,都会从另一端发送出去。veth 实现了不同 namespace 的网络数据传输。 116 | 117 | ![Docker Bridge](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230424143547.png) 118 | 119 | 在 Docker 中,宿主机的 veth 端会桥接到网桥中,接收到容器中的 veth 端发过来的数据后会经由网桥 docker0 再转发到宿主机网卡 eth0,最终通过 eth0 发送数据。当然在发送数据前,需要经过 iptables MASQUERADE 规则将源地址改成宿主机 ip,这样才能接收到响应数据包。而宿主机网卡接收到的数据会通过 iptables DNAT 根据端口号修改目的地址和端口为容器的 ip 和端口,然后根据路由规则发送到网桥 docker0 中,并最终由网桥 docker0 发送到对应的容器中。 120 | 121 | Docker 里面网络模式分为 bridge,host,overlay 等几种模式,默认是采用 bridge 模式网络如图所示。如果使用 host 模式,则不隔离直接使用宿主机网络。overlay 网络则是更加高级的模式,可以实现跨主机的容器通信。 122 | 123 | # USER Namespace 124 | 125 | user namespace 用于隔离用户和组信息,在不同的 namespace 中用户可以有相同的 UID 和 GID,它们之间互相不影响。父子 namespace 之间可以进行用户映射,如父 namespace(宿主机)的普通用户映射到子 namespace(容器)的 root 用户,以减少子 namespace 的 root 用户操作父 namespace 的风险。user namespace 功能虽然在很早就出现了,但是直到 Linux kernel 3.8 之后这个功能才趋于完善。 126 | 127 | 创建新的 user namespace 之后第一步就是设置好 user 和 group 的映射关系。这个映射通过设置 `/proc/PID/uid_map(gid_map)` 实现,格式如下,ID-inside-ns 是容器内的 uid/gid,而 ID-outside-ns 则是容器外映射的真实 uid/gid。比如 0 1000 1 表示将真实的 uid=1000 映射为容器内的 uid=0,length 为映射的范围。 128 | 129 | ```s 130 | ID-inside-ns ID-outside-ns length 131 | ``` 132 | 133 | 不是所有的进程都能随便修改映射文件的,必须同时具备如下条件: 134 | 135 | - 修改映射文件的进程必须有 PID 进程所在 user namespace 的 CAP_SETUID/CAP_SETGID 权限。 136 | 137 | - 修改映射文件的进程必须是跟 PID 在同一个 user namespace 或者 PID 的父 namespace。 138 | 139 | - 映射文件 uid_map 和 gid_map 只能写入一次,再次写入会报错。 140 | 141 | Docker1.10 之后的版本可以通过在 docker daemon 启动时加上 `--userns-remap=[USERNAME]` 来实现 USER Namespace 的隔离。我们指定了 username=test 启动 dockerd,查看 subuid 文件可以发现 test 映射的 uid 范围是 165536 到 165536+65536= 231072,而且在 docker 目录下面对应 test 有一个独立的目录 165536.165536 存在。 142 | 143 | ```s 144 | root@host:/home/vagrant# cat /etc/subuid 145 | vagrant:100000:65536 146 | test:165536:65536 147 | 148 | root@host:/home/vagrant# ls /var/lib/docker/165536.165536/ 149 | builder/ containerd/ containers/ image/ network/ ... 150 | ``` 151 | 152 | 运行 `docker images -a` 等命令可以发现在启用 user namespace 之前的镜像都看不到了。此时只能看到在新的 user namespace 里面创建的 docker 镜像和容器。而此时我们创建一个测试容器,可以在容器外看到容器进程的 uid_map 已经设置为 ssj,这样容器中的 root 用户映射到宿主机就是 test 这个用户了,此时如果要删除我们挂载的/bin 目录中的文件,会提示没有权限,增强了安全性。 153 | 154 | ```s 155 | ### dockerd 启动时加了 --userns-remap=test 156 | root@host:/home/vagrant# docker run -it -v /bin:/host/bin --name demo alpine /bin/ash 157 | / # rm /host/bin/which 158 | rm: remove '/host/bin/which'? y 159 | rm: can't remove '/host/bin/which': Permission denied 160 | 161 | ### 宿主机查看容器进程uid_map文件 162 | root@host:/home/vagrant# CPID=`ps -ef|grep '\/bin\/ash'|awk '{printf $2}'` 163 | root@host:/home/vagrant# cat /proc/$CPID/uid_map 164 | 0 165536 65536 165 | ``` 166 | 167 | # 其他 Namespace 168 | 169 | UTS namespace 用于隔离主机名等。可以看到在新的 uts namespace 修改主机名并不影响原 namespace 的主机名。 170 | 171 | ```s 172 | root@host:/home/vagrant# unshare --uts --fork bash 173 | root@host:/home/vagrant# hostname 174 | host 175 | root@host:/home/vagrant# hostname modified 176 | root@host:/home/vagrant# hostname 177 | modified 178 | root@host:/home/vagrant# exit 179 | root@host:/home/vagrant# hostname 180 | host 181 | ``` 182 | 183 | IPC Namespace 用于隔离 IPC 消息队列等。可以看到,新老 ipc namespace 的消息队列互不影响。 184 | 185 | ```s 186 | root@host:/home/vagrant# ipcmk -Q 187 | Message queue id: 0 188 | root@host:/home/vagrant# ipcs -q 189 | 190 | ------ Message Queues -------- 191 | key msqid owner perms used-bytes messages 192 | 0x26c3371c 0 root 644 0 0 193 | 194 | root@host:/home/vagrant# unshare --ipc --fork bash 195 | root@host:/home/vagrant# ipcs -q 196 | 197 | ------ Message Queues -------- 198 | key msqid owner perms used-bytes messages 199 | ``` 200 | 201 | CGROUP Namespace 是 Linux4.6 以后才支持的新 namespace。容器技术使用 namespace 和 cgroup 实现环境隔离和资源限制,但是对于 cgroup 本身并没有隔离。没有 cgroup namespace 前,容器中一旦挂载 cgroup 文件系统,便可以修改整全局的 cgroup 配置。有了 cgroup namespace 后,每个 namespace 中的进程都有自己的 cgroup 文件系统视图,增强了安全性,同时也让容器迁移更加方便。 202 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/04~网络/网络配置.md: -------------------------------------------------------------------------------- 1 | # Docker 网络配置 2 | 3 | Docker 1.9 中正式引入了所谓的 "Container Network Model",也就适合所谓的 [CNM](https://blog.docker.com/2015/04/docker-networking-takes-a-step-in-the-right-direction-2/) 的概念。CNM 即用于创建小型微分割的网络来使得不同组的容器之间进行相互连接。 4 | 5 | ![img](https://blog.docker.com/media/2015/04/cnm-model.jpg) 6 | 7 | ## Basic Usage | 基本使用 8 | 9 | ### 端口映射 10 | 11 | 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 `-P`或`-p` 参数来指定端口映射。 12 | 13 | 当使用 -P 标记时,Docker 会随机映射一个 `49000~49900` 的端口到内部容器开放的网络端口。 14 | 15 | 使用 `docker ps` 可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。 16 | 17 | ```sh 18 | $ sudo docker run -d -P training/webapp python app.py 19 | 20 | $ sudo docker ps -l 21 | 22 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESbc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse 23 | ``` 24 | 25 | 同样的,可以通过 `docker logs` 命令来查看应用的信息。 26 | 27 | ```sh 28 | $ sudo docker logs -f nostalgic_morse 29 | 30 | * Running on http://0.0.0.0:5000/10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 - 31 | ``` 32 | 33 | -p (小写的)则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort`。 34 | 35 | - 映射所有接口地址 36 | 37 | 使用 `hostPort:containerPort` 格式本地的 5000 端口映射到容器的 5000 端口,可以执行 38 | 39 | ``` 40 | $ sudo docker run -d -p 5000:5000 training/webapp python app.py 41 | ``` 42 | 43 | 此时默认会绑定本地所有接口上的所有地址。 44 | 45 | - 映射到指定地址的指定端口 46 | 47 | 可以使用 `ip:hostPort:containerPort` 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1 48 | 49 | ``` 50 | $ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py 51 | ``` 52 | 53 | - 映射到指定地址的任意端口 54 | 55 | 使用 `ip::containerPort` 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。 56 | 57 | ``` 58 | $ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py 59 | ``` 60 | 61 | 还可以使用 udp 标记来指定 udp 端口 62 | 63 | ``` 64 | $ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py 65 | ``` 66 | 67 | - 查看映射端口配置 68 | 69 | 使用 `docker port` 来查看当前映射的端口配置,也可以查看到绑定的地址 70 | 71 | ``` 72 | $ docker port nostalgic_morse 5000127.0.0.1:49155. 73 | ``` 74 | 75 | 注意: 76 | 77 | - 容器有自己的内部网络和 ip 地址(使用 `docker inspect` 可以获取所有的变量,Docker 还可以有一个可变的网络配置。) 78 | - -p 标记可以多次使用来绑定多个端口 79 | 80 | 例如 81 | 82 | ``` 83 | $ sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py 84 | ``` 85 | 86 | 注意,网络映射的操作只会在 run 命令中起作用,如果已经运行了一个容器,需要重新设置其网络映射情况,请使用 commit 将容器转化为镜像之后再创建新的容器。 87 | 88 | ### 容器互联 (Links) 89 | 90 | 容器的连接(linking )系统是除了端口映射外,另一种跟容器中应用交互的方式。 91 | 92 | 该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。 93 | 94 | #### 自定义容器命名 95 | 96 | 连接系统依据容器的名称来执行。因此,首先需要自定义一个好记的容器命名。 97 | 98 | 虽然当创建容器的时候,系统默认会分配一个名字。自定义命名容器有 2 个好处: 99 | 100 | - 自定义的命名,比较好记,比如一个 web 应用容器我们可以给它起名叫 web 101 | - 当要连接其他容器时候,可以作为一个有用的参考点,比如连接 web 容器到 db 容器 102 | 103 | 使用 `--name` 标记可以为容器自定义命名。 104 | 105 | ``` 106 | $ sudo docker run -d -P --name web training/webapp python app.py 107 | ``` 108 | 109 | 使用 `docker ps` 来验证设定的命名。 110 | 111 | ``` 112 | $ sudo docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web 113 | ``` 114 | 115 | 也可以使用 `docker inspect` 来查看容器的名字 116 | 117 | ``` 118 | $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde/web 119 | ``` 120 | 121 | 注意:容器的名称是唯一的。如果已经命名了一个叫 web 的容器,当你要再次使用 web 这个名称的时候,需要先用`docker rm` 来删除之前创建的同名容器。 122 | 123 | 在执行 `docker run` 的时候如果添加 `--rm` 标记,则容器在终止后会立刻删除。注意,`--rm` 和 `-d` 参数不能同时使用。 124 | 125 | #### 容器互联 126 | 127 | 使用 `--link` 参数可以让容器之间安全的进行交互。 128 | 129 | 下面先创建一个新的数据库容器。 130 | 131 | ``` 132 | $ sudo docker run -d --name db training/postgres 133 | ``` 134 | 135 | 删除之前创建的 web 容器 136 | 137 | ``` 138 | $ docker rm -f web 139 | ``` 140 | 141 | 然后创建一个新的 web 容器,并将它连接到 db 容器 142 | 143 | ``` 144 | $ sudo docker run -d -P --name web --link db:db training/webapp python app.py 145 | ``` 146 | 147 | 此时,db 容器和 web 容器建立互联关系。 148 | 149 | `--link` 参数的格式为 `--link name:alias`,其中 `name` 是要链接的容器的名称,`alias`是这个连接的别名。 150 | 151 | 使用 `docker ps` 来查看容器的连接 152 | 153 | ``` 154 | $ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db, web/dbaed84ee21bde training/webapp:latest python app.py 16 hours ago Up 2 minutes 0.0.0.0:49154->5000/tcp web 155 | ``` 156 | 157 | 可以看到自定义命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。 158 | 159 | Docker 在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主主机上。在启动 db 容器的时候并没有使用 `-p` 和 `-P` 标记,从而避免了暴露数据库端口到外部网络上。 160 | 161 | Docker 通过 2 种方式为容器公开连接信息: 162 | 163 | - 环境变量 164 | - 更新 `/etc/hosts` 文件 165 | 166 | 使用 `env` 命令来查看 web 容器的环境变量 167 | 168 | ``` 169 | $ sudo docker run --rm --name web2 --link db:db training/webapp env. . .DB_NAME=/web2/dbDB_PORT=tcp://172.17.0.5:5432DB_PORT_5000_TCP=tcp://172.17.0.5:5432DB_PORT_5000_TCP_PROTO=tcpDB_PORT_5000_TCP_PORT=5432DB_PORT_5000_TCP_ADDR=172.17.0.5. . . 170 | ``` 171 | 172 | 其中 DB\_ 开头的环境变量是供 web 容器连接 db 容器使用,前缀采用大写的连接别名。 173 | 174 | 除了环境变量,Docker 还添加 host 信息到父容器的 `/etc/hosts` 的文件。下面是父容器 web 的 hosts 文件 175 | 176 | ``` 177 | $ sudo docker run -t -i --rm --link db:db training/webapp /bin/bashroot@aed84ee21bde:/opt/webapp# cat /etc/hosts172.17.0.7 aed84ee21bde. . .172.17.0.5 db 178 | ``` 179 | 180 | 这里有 2 个 hosts,第一个是 web 容器,web 容器用 id 作为他的主机名,第二个是 db 容器的 ip 和主机名。可以在 web 容器中安装 ping 命令来测试跟 db 容器的连通。 181 | 182 | ``` 183 | root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-pingroot@aed84ee21bde:/opt/webapp# ping dbPING db (172.17.0.5): 48 data bytes56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms 184 | ``` 185 | 186 | 用 ping 来测试 db 容器,它会解析成 `172.17.0.5`。\* 注意:官方的 ubuntu 镜像默认没有安装 ping,需要自行安装。 187 | 188 | 用户可以链接多个父容器到子容器,比如可以链接多个 web 到 db 容器上。 189 | 190 | ## Networking( 网络 ) 191 | 192 | 在 Docker 1.9 之后,Docker 正式宣布可以将 Networking 应用于生产环境中,并且可以与 Swarm 以及 Compose 进行较好的结合。与传统的 List 相比,Networking 具有以下优势: 193 | 194 | - 允许不同物理主机或者虚拟主机上的容器进行通信 195 | - 使用了 Networking 的容器可以很方便地进行停止、启动或者重启等操作而不用担心会影响到与其他容器之间的连接 196 | - 并不需要在连接到某个容器之前就直接创建它,换言之,Networking 不再像原本的 List 一样会依赖某个容器而存在 197 | 198 | ![img](http://dockerone.com/uploads/article/20151106/4f9a1786e78de4e9b3d6db5df9afbeeb.jpg) 199 | 200 | ### Single Host Networking 201 | 202 | 可以直接使用`docker network`命令来使用 Networking,其中可以使用`docker network create`来创建一个新的网络,在这个示例中,我们会创建一个叫做`frontend`的网络并且在其中运行一个 nginx 容器: 203 | 204 | ``` 205 | $ docker network create frontend$ docker run -itd --net=frontend --name web nginx 206 | ``` 207 | 208 | 我们使用网络来分割应用,乃至于分割应用中的不同模块。在本例子中,我们可以创建另一个包含了应用程序的网络`app`,然后将这个网络与`frontend`网络相连,命令如下所示: 209 | 210 | ``` 211 | $ docker network create app$ docker run -itd --name myapp --net=app $ docker network connect app web 212 | ``` 213 | 214 | 这样我们的 Nginx 服务器就可以使用`myapp.app`这个主机名来连接到应用程序中。我们创建两个基于 busybox 的容器来进行尝试: 215 | 216 | ``` 217 | $ docker run -d --name rose --net=frontend busybox topc1fa2dc7fa3a412b52b53f5facd25ba11e99c362d77be8cea4ff49f3d5e2cafc$ docker run --rm --net=frontend busybox ping -c 4 rosePING rose (172.19.0.2): 56 data bytes64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.122 ms64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.078 ms64 bytes from 172.19.0.2: seq=2 ttl=64 time=0.098 ms64 bytes from 172.19.0.2: seq=3 ttl=64 time=0.241 ms 218 | ``` 219 | 220 | 我们已将第二个容器附着到`frontend`网路中,并且用`ping`命令来进行内建的容器发现,我们可以用`inspect`命令来查看网络的详情: 221 | 222 | ``` 223 | $ docker network inspect frontend[ { "Name": "frontend", "Id": "a639a457122020faa69a4ab906bc33217c9c6d73048f3dbbb69e53dbe5e0952c", "Scope": "local", "Driver": "bridge", "IPAM": { "Driver": "default", "Config": [ {} ] }, "Containers": { "c1fa2dc7fa3a412b52b53f5facd25ba11e99c362d77be8cea4ff49f3d5e2cafc": { "EndpointID": "976bab21d4a11cd21d5d1c1560f67f39ef15245662aeacf097eb1d5c148ed748", "MacAddress": "02:42:ac:13:00:02", "IPv4Address": "172.19.0.2/16", "IPv6Address": "" } }, "Options": {} }] 224 | ``` 225 | 226 | 在前端网络之外,我们也可以创建一个自定义的后端网络,用于连接其他容器: 227 | 228 | ``` 229 | $ docker network create backend09733cac7890edca439cdc3d476b4cd1959e44065217aa581d359575b8d2288f$ docker network connect backend rose$ docker network inspect backend { "name": "backend", "id": "09733cac7890edca439cdc3d476b4cd1959e44065217aa581d359575b8d2288f", "scope": "local", "driver": "bridge", "ipam": { "driver": "default", "config": [ {} ] }, "containers": { "c1fa2dc7fa3a412b52b53f5facd25ba11e99c362d77be8cea4ff49f3d5e2cafc": { "endpoint": "438730c588915dd54dc694efdb3a15c77bc5e86c744f5f87a65f6ac46b43e5ad", "mac_address": "02:42:ac:14:00:02", "ipv4_address": "172.20.0.2/16", "ipv6_address": "" } }, "options": {} }] 230 | ``` 231 | 232 | 再看一下容器中具体的网络的设置: 233 | 234 | ``` 235 | $ docker inspect -f '{{ json .NetworkSettings }}' rose{ "Bridge": "", "SandboxID": "b600bebe1e2bb6dee92335e6acfe49215c30c4964d7a982711ec12c6acca3309", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": {}, "SandboxKey": "/var/run/docker/netns/b600bebe1e2b", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "", "Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "MacAddress": "", "Networks": { "backend": { "EndpointID": "438730c588915dd54dc694efdb3a15c77bc5e86c744f5f87a65f6ac46b43e5ad", "Gateway": "172.20.0.1", "IPAddress": "172.20.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:14:00:02" }, "frontend": { "EndpointID": "976bab21d4a11cd21d5d1c1560f67f39ef15245662aeacf097eb1d5c148ed748", "Gateway": "172.19.0.1", "IPAddress": "172.19.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:13:00:02" } }} 236 | ``` 237 | 238 | 而在容器中使用`ifconfig`命令查看时: 239 | 240 | ``` 241 | $ docker exec rose ifconifgeth0 Link encap:Ethernet HWaddr 02:42:AC:13:00:02 inet addr:172.19.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:27 errors:0 dropped:0 overruns:0 frame:0 TX packets:16 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2238 (2.1 KiB) TX bytes:1208 (1.1 KiB)eth1 Link encap:Ethernet HWaddr 02:42:AC:14:00:02 inet addr:172.20.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:16 errors:0 dropped:0 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:1296 (1.2 KiB) TX bytes:648 (648.0 B)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) 242 | ``` 243 | 244 | 而如果要将某个容器从网络中断开时,可以使用如下命令: 245 | 246 | ``` 247 | $ docker network disconnect backend rose 248 | ``` 249 | 250 | 这种网络配置方式的初衷即将服务独立出来成为一个网络中不同的容器之间相互交流的唯一信息。换言之,在实践中,需要构建出大量的小型网络,每个网络中只负责某个服务的通信,并且不同的网络之间应该是相互隔离的。一个典型的例子就是负载均衡器、前端、后端以及一个数据库。 251 | 252 | ![img](http://www.container42.com/assets/cnm-demo.png) 253 | 254 | #### Network Customization 255 | 256 | 在创建网络的时候我们也可以设置很多的自定义的配置: 257 | 258 | ``` 259 | --aux-address=map[] auxiliary ipv4 or ipv6 addresses used by Network driver -d, --driver="bridge" Driver to manage the Network --gateway=[] ipv4 or ipv6 Gateway for the master subnet --help=false Print usage --ip-range=[] allocate container ip from a sub-range --ipam-driver=default IP Address Management Driver -o, --opt=map[] set driver specific options --subnet=[] subnet in CIDR format that represents a network segment 260 | ``` 261 | 262 | 我们首先来看下`--driver`选项,该选项可以来选定具体的网络驱动方式来管理网络,目前 Docker 提供了如下两种驱动器: 263 | 264 | - bridge -- This driver provides the same sort of networking via veth bridge devices that prior versions of docker use, it is the default. 265 | - overlay -- Not to be confused with the "overlay" storage driver (thanks overlayfs), this driver provides native multi-host networking for docker clusters. When using swarm, this is the default driver. 266 | 267 | 另一个常用的选项是`--ipam-driver`,用来确定 IP 地址是如何分配的。目前 Docker 仅内置了一种 IP 地址分配方案,未来会添加上 DHCP IAMP 方式。 268 | 269 | ### Multi-Host Networking 270 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 2 | Public License 3 | 4 | By exercising the Licensed Rights (defined below), You accept and agree 5 | to be bound by the terms and conditions of this Creative Commons 6 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 7 | ("Public License"). To the extent this Public License may be 8 | interpreted as a contract, You are granted the Licensed Rights in 9 | consideration of Your acceptance of these terms and conditions, and the 10 | Licensor grants You such rights in consideration of benefits the 11 | Licensor receives from making the Licensed Material available under 12 | these terms and conditions. 13 | 14 | 15 | Section 1 -- Definitions. 16 | 17 | a. Adapted Material means material subject to Copyright and Similar 18 | Rights that is derived from or based upon the Licensed Material 19 | and in which the Licensed Material is translated, altered, 20 | arranged, transformed, or otherwise modified in a manner requiring 21 | permission under the Copyright and Similar Rights held by the 22 | Licensor. For purposes of this Public License, where the Licensed 23 | Material is a musical work, performance, or sound recording, 24 | Adapted Material is always produced where the Licensed Material is 25 | synched in timed relation with a moving image. 26 | 27 | b. Adapter's License means the license You apply to Your Copyright 28 | and Similar Rights in Your contributions to Adapted Material in 29 | accordance with the terms and conditions of this Public License. 30 | 31 | c. BY-NC-SA Compatible License means a license listed at 32 | creativecommons.org/compatiblelicenses, approved by Creative 33 | Commons as essentially the equivalent of this Public License. 34 | 35 | d. Copyright and Similar Rights means copyright and/or similar rights 36 | closely related to copyright including, without limitation, 37 | performance, broadcast, sound recording, and Sui Generis Database 38 | Rights, without regard to how the rights are labeled or 39 | categorized. For purposes of this Public License, the rights 40 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 41 | Rights. 42 | 43 | e. Effective Technological Measures means those measures that, in the 44 | absence of proper authority, may not be circumvented under laws 45 | fulfilling obligations under Article 11 of the WIPO Copyright 46 | Treaty adopted on December 20, 1996, and/or similar international 47 | agreements. 48 | 49 | f. Exceptions and Limitations means fair use, fair dealing, and/or 50 | any other exception or limitation to Copyright and Similar Rights 51 | that applies to Your use of the Licensed Material. 52 | 53 | g. License Elements means the license attributes listed in the name 54 | of a Creative Commons Public License. The License Elements of this 55 | Public License are Attribution, NonCommercial, and ShareAlike. 56 | 57 | h. Licensed Material means the artistic or literary work, database, 58 | or other material to which the Licensor applied this Public 59 | License. 60 | 61 | i. Licensed Rights means the rights granted to You subject to the 62 | terms and conditions of this Public License, which are limited to 63 | all Copyright and Similar Rights that apply to Your use of the 64 | Licensed Material and that the Licensor has authority to license. 65 | 66 | j. Licensor means the individual(s) or entity(ies) granting rights 67 | under this Public License. 68 | 69 | k. NonCommercial means not primarily intended for or directed towards 70 | commercial advantage or monetary compensation. For purposes of 71 | this Public License, the exchange of the Licensed Material for 72 | other material subject to Copyright and Similar Rights by digital 73 | file-sharing or similar means is NonCommercial provided there is 74 | no payment of monetary compensation in connection with the 75 | exchange. 76 | 77 | l. Share means to provide material to the public by any means or 78 | process that requires permission under the Licensed Rights, such 79 | as reproduction, public display, public performance, distribution, 80 | dissemination, communication, or importation, and to make material 81 | available to the public including in ways that members of the 82 | public may access the material from a place and at a time 83 | individually chosen by them. 84 | 85 | m. Sui Generis Database Rights means rights other than copyright 86 | resulting from Directive 96/9/EC of the European Parliament and of 87 | the Council of 11 March 1996 on the legal protection of databases, 88 | as amended and/or succeeded, as well as other essentially 89 | equivalent rights anywhere in the world. 90 | 91 | n. You means the individual or entity exercising the Licensed Rights 92 | under this Public License. Your has a corresponding meaning. 93 | 94 | 95 | Section 2 -- Scope. 96 | 97 | a. License grant. 98 | 99 | 1. Subject to the terms and conditions of this Public License, 100 | the Licensor hereby grants You a worldwide, royalty-free, 101 | non-sublicensable, non-exclusive, irrevocable license to 102 | exercise the Licensed Rights in the Licensed Material to: 103 | 104 | a. reproduce and Share the Licensed Material, in whole or 105 | in part, for NonCommercial purposes only; and 106 | 107 | b. produce, reproduce, and Share Adapted Material for 108 | NonCommercial purposes only. 109 | 110 | 2. Exceptions and Limitations. For the avoidance of doubt, where 111 | Exceptions and Limitations apply to Your use, this Public 112 | License does not apply, and You do not need to comply with 113 | its terms and conditions. 114 | 115 | 3. Term. The term of this Public License is specified in Section 116 | 6(a). 117 | 118 | 4. Media and formats; technical modifications allowed. The 119 | Licensor authorizes You to exercise the Licensed Rights in 120 | all media and formats whether now known or hereafter created, 121 | and to make technical modifications necessary to do so. The 122 | Licensor waives and/or agrees not to assert any right or 123 | authority to forbid You from making technical modifications 124 | necessary to exercise the Licensed Rights, including 125 | technical modifications necessary to circumvent Effective 126 | Technological Measures. For purposes of this Public License, 127 | simply making modifications authorized by this Section 2(a) 128 | (4) never produces Adapted Material. 129 | 130 | 5. Downstream recipients. 131 | 132 | a. Offer from the Licensor -- Licensed Material. Every 133 | recipient of the Licensed Material automatically 134 | receives an offer from the Licensor to exercise the 135 | Licensed Rights under the terms and conditions of this 136 | Public License. 137 | 138 | b. Additional offer from the Licensor -- Adapted Material. 139 | Every recipient of Adapted Material from You 140 | automatically receives an offer from the Licensor to 141 | exercise the Licensed Rights in the Adapted Material 142 | under the conditions of the Adapter's License You apply. 143 | 144 | c. No downstream restrictions. You may not offer or impose 145 | any additional or different terms or conditions on, or 146 | apply any Effective Technological Measures to, the 147 | Licensed Material if doing so restricts exercise of the 148 | Licensed Rights by any recipient of the Licensed 149 | Material. 150 | 151 | 6. No endorsement. Nothing in this Public License constitutes or 152 | may be construed as permission to assert or imply that You 153 | are, or that Your use of the Licensed Material is, connected 154 | with, or sponsored, endorsed, or granted official status by, 155 | the Licensor or others designated to receive attribution as 156 | provided in Section 3(a)(1)(A)(i). 157 | 158 | b. Other rights. 159 | 160 | 1. Moral rights, such as the right of integrity, are not 161 | licensed under this Public License, nor are publicity, 162 | privacy, and/or other similar personality rights; however, to 163 | the extent possible, the Licensor waives and/or agrees not to 164 | assert any such rights held by the Licensor to the limited 165 | extent necessary to allow You to exercise the Licensed 166 | Rights, but not otherwise. 167 | 168 | 2. Patent and trademark rights are not licensed under this 169 | Public License. 170 | 171 | 3. To the extent possible, the Licensor waives any right to 172 | collect royalties from You for the exercise of the Licensed 173 | Rights, whether directly or through a collecting society 174 | under any voluntary or waivable statutory or compulsory 175 | licensing scheme. In all other cases the Licensor expressly 176 | reserves any right to collect such royalties, including when 177 | the Licensed Material is used other than for NonCommercial 178 | purposes. 179 | 180 | 181 | Section 3 -- License Conditions. 182 | 183 | Your exercise of the Licensed Rights is expressly made subject to the 184 | following conditions. 185 | 186 | a. Attribution. 187 | 188 | 1. If You Share the Licensed Material (including in modified 189 | form), You must: 190 | 191 | a. retain the following if it is supplied by the Licensor 192 | with the Licensed Material: 193 | 194 | i. identification of the creator(s) of the Licensed 195 | Material and any others designated to receive 196 | attribution, in any reasonable manner requested by 197 | the Licensor (including by pseudonym if 198 | designated); 199 | 200 | ii. a copyright notice; 201 | 202 | iii. a notice that refers to this Public License; 203 | 204 | iv. a notice that refers to the disclaimer of 205 | warranties; 206 | 207 | v. a URI or hyperlink to the Licensed Material to the 208 | extent reasonably practicable; 209 | 210 | b. indicate if You modified the Licensed Material and 211 | retain an indication of any previous modifications; and 212 | 213 | c. indicate the Licensed Material is licensed under this 214 | Public License, and include the text of, or the URI or 215 | hyperlink to, this Public License. 216 | 217 | 2. You may satisfy the conditions in Section 3(a)(1) in any 218 | reasonable manner based on the medium, means, and context in 219 | which You Share the Licensed Material. For example, it may be 220 | reasonable to satisfy the conditions by providing a URI or 221 | hyperlink to a resource that includes the required 222 | information. 223 | 3. If requested by the Licensor, You must remove any of the 224 | information required by Section 3(a)(1)(A) to the extent 225 | reasonably practicable. 226 | 227 | b. ShareAlike. 228 | 229 | In addition to the conditions in Section 3(a), if You Share 230 | Adapted Material You produce, the following conditions also apply. 231 | 232 | 1. The Adapter's License You apply must be a Creative Commons 233 | license with the same License Elements, this version or 234 | later, or a BY-NC-SA Compatible License. 235 | 236 | 2. You must include the text of, or the URI or hyperlink to, the 237 | Adapter's License You apply. You may satisfy this condition 238 | in any reasonable manner based on the medium, means, and 239 | context in which You Share Adapted Material. 240 | 241 | 3. You may not offer or impose any additional or different terms 242 | or conditions on, or apply any Effective Technological 243 | Measures to, Adapted Material that restrict exercise of the 244 | rights granted under the Adapter's License You apply. 245 | 246 | 247 | Section 4 -- Sui Generis Database Rights. 248 | 249 | Where the Licensed Rights include Sui Generis Database Rights that 250 | apply to Your use of the Licensed Material: 251 | 252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 253 | to extract, reuse, reproduce, and Share all or a substantial 254 | portion of the contents of the database for NonCommercial purposes 255 | only; 256 | 257 | b. if You include all or a substantial portion of the database 258 | contents in a database in which You have Sui Generis Database 259 | Rights, then the database in which You have Sui Generis Database 260 | Rights (but not its individual contents) is Adapted Material, 261 | including for purposes of Section 3(b); and 262 | 263 | c. You must comply with the conditions in Section 3(a) if You Share 264 | all or a substantial portion of the contents of the database. 265 | 266 | For the avoidance of doubt, this Section 4 supplements and does not 267 | replace Your obligations under this Public License where the Licensed 268 | Rights include other Copyright and Similar Rights. 269 | 270 | 271 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 272 | 273 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 274 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 275 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 276 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 277 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 278 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 279 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 280 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 281 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 282 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 283 | 284 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 285 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 286 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 287 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 288 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 289 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 290 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 291 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 292 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 293 | 294 | c. The disclaimer of warranties and limitation of liability provided 295 | above shall be interpreted in a manner that, to the extent 296 | possible, most closely approximates an absolute disclaimer and 297 | waiver of all liability. 298 | 299 | 300 | Section 6 -- Term and Termination. 301 | 302 | a. This Public License applies for the term of the Copyright and 303 | Similar Rights licensed here. However, if You fail to comply with 304 | this Public License, then Your rights under this Public License 305 | terminate automatically. 306 | 307 | b. Where Your right to use the Licensed Material has terminated under 308 | Section 6(a), it reinstates: 309 | 310 | 1. automatically as of the date the violation is cured, provided 311 | it is cured within 30 days of Your discovery of the 312 | violation; or 313 | 314 | 2. upon express reinstatement by the Licensor. 315 | 316 | For the avoidance of doubt, this Section 6(b) does not affect any 317 | right the Licensor may have to seek remedies for Your violations 318 | of this Public License. 319 | 320 | c. For the avoidance of doubt, the Licensor may also offer the 321 | Licensed Material under separate terms or conditions or stop 322 | distributing the Licensed Material at any time; however, doing so 323 | will not terminate this Public License. 324 | 325 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 326 | License. 327 | 328 | 329 | Section 7 -- Other Terms and Conditions. 330 | 331 | a. The Licensor shall not be bound by any additional or different 332 | terms or conditions communicated by You unless expressly agreed. 333 | 334 | b. Any arrangements, understandings, or agreements regarding the 335 | Licensed Material not stated herein are separate from and 336 | independent of the terms and conditions of this Public License. 337 | 338 | 339 | Section 8 -- Interpretation. 340 | 341 | a. For the avoidance of doubt, this Public License does not, and 342 | shall not be interpreted to, reduce, limit, restrict, or impose 343 | conditions on any use of the Licensed Material that could lawfully 344 | be made without permission under this Public License. 345 | 346 | b. To the extent possible, if any provision of this Public License is 347 | deemed unenforceable, it shall be automatically reformed to the 348 | minimum extent necessary to make it enforceable. If the provision 349 | cannot be reformed, it shall be severed from this Public License 350 | without affecting the enforceability of the remaining terms and 351 | conditions. 352 | 353 | c. No term or condition of this Public License will be waived and no 354 | failure to comply consented to unless expressly agreed to by the 355 | Licensor. 356 | 357 | d. Nothing in this Public License constitutes or may be interpreted 358 | as a limitation upon, or waiver of, any privileges and immunities 359 | that apply to the Licensor or You, including from the legal 360 | processes of any jurisdiction or authority. 361 | -------------------------------------------------------------------------------- /01~虚拟化/Docker/99~参考资料/2023-无限咪咪-Docker 与 Kubernetes小记.md: -------------------------------------------------------------------------------- 1 | # Docker & Kubernetes 小记 2 | 3 | # 第一章:Docker 简介与安装 4 | 5 | - **Docker 是什么?** 6 | 7 | Docker 是一个**生态**,这个生态专注于管理 Containers(容器)。 8 | 9 | - **为什么用 Docker?** 10 | 11 | Docker 使得安装软件与运行软件十分便利。 12 | 13 | 安装软件时,可能会碰到各种各样的问题,解决这些问题可能会花费很多人力物力。而 Docker 就是想标准化“安装流程”,让用户直接进入运行软件步骤,节省用户成本。 14 | 15 | 课程中使用安装 Redis 作为例子,来展示普通安装和使用 Docker 启动的效率差别。 16 | 17 | - **基础概念** 18 | 19 | 1. Image 20 | 21 | Image 也称为**镜像**,是一个包含运行指定程序的文件系统快照和启动命令的文件。通常在 Docker Hub(https://hub.docker.com/) 下载。 22 | 23 | 2. Container 24 | 25 | Container 也就是**容器**,是 Image 的一个实例。是一个正在运行的进程,以及机器物理资源的子集。 26 | 27 | 不同的 Containers 之间,资源是隔离的。 28 | 29 | 3. Docker Client 30 | 31 | 用户通过终端与 Docker Client 交互,一个解析用户命令的程序,并发送给 Docker Server。 32 | 33 | 这个程序本身没有处理 Image 与 Container 的功能。 34 | 35 | 4. Docker Server 36 | 37 | 也称之为 Docker Daemon,是负责创建、运行容器的程序。 38 | 39 | - **安装** 40 | 41 | 官方下载网站:https://docs.docker.com/get-docker/ 42 | 43 | 注册后安装对应版本。 44 | 45 | ⚠️ 注意:安装同时包含了 Linux 虚拟机 46 | 47 | - **docker version** 48 | 49 | 安装完成后,在终端输入 **docker version**,如果输出相关信息说明安装成功了。 50 | 51 | - **启动第一个容器** 52 | 53 | 在终端输入 **docker run hello-world**,之后终端会输出如下信息: 54 | 55 | ```shell 56 | Unable to find image 'hello-world:latest' locally 57 | latest: Pulling from library/hello-world 58 | latest: Pulling from library/hello-world 59 | 7050e35b49f5: Pull complete 60 | Digest: sha256:94ebc7edf3401f299cd3376a1669bc0a49aef92d6d2669005f9bc5ef028dc333 61 | Status: Downloaded newer image for hello-world:latest 62 | 63 | Hello from Docker! 64 | This message shows that your installation appears to be working correctly. 65 | 66 | To generate this message, Docker took the following steps: 67 | 1. The Docker client contacted the Docker daemon. 68 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 69 | (arm64v8) 70 | 3. The Docker daemon created a new container from that image which runs the 71 | executable that produces the output you are currently reading. 72 | 4. The Docker daemon streamed that output to the Docker client, which sent it 73 | to your terminal. 74 | 75 | To try something more ambitious, you can run an Ubuntu container with: 76 | $ docker run -it ubuntu bash 77 | 78 | Share images, automate workflows, and more with a free Docker ID: 79 | https://hub.docker.com/ 80 | 81 | For more examples and ideas, visit: 82 | https://docs.docker.com/get-started/ 83 | ``` 84 | 85 | hello-world 是 Docker 社区维护的一个官方镜像,作用就是演示 Docker 化。 86 | 87 | Docker Client 收到终端发来的命令后,检查处理后发送给 Docker Server 来创建容器。创建时会先在本地查找镜像缓存,如果缓存中没有,就会去 Docker Hub 上下载镜像后再继续。 88 | 89 | # 第二章:了解 Docker Client 中基本命令 90 | 91 | 本章通过学习 8 个命令的基础使用方式来熟悉 Docker Client。 92 | 93 | - **docker run** 94 | 95 | 第一章启动容器时我们使用了 **docker run** 命令,这个命令会先根据镜像中的快照创建容器,然后再用镜像中的启动命令启动容器。 96 | 97 | 该命令的使用格式如下: 98 | 99 | ```shell 100 | docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 101 | ``` 102 | 103 | 除了第一章中的用法,还可以在镜像名称后,[COMMAND] 处添加自定义启动命令: 104 | 105 | ```shell 106 | wuxianmimi ~ % docker run busybox ls 107 | bin 108 | dev 109 | etc 110 | home 111 | lib 112 | lib64 113 | proc 114 | root 115 | sys 116 | tmp 117 | usr 118 | var 119 | ``` 120 | 121 | echo 信息: 122 | 123 | ```shell 124 | wuxianmimi ~ % docker run busybox echo wuxianmimi 125 | wuxianmimi 126 | wuxianmimi ~ % 127 | ``` 128 | 129 | ⚠️:使用自定义启动命令后会覆盖默认启动命令。 130 | 131 | 上面的例子使用的是 busybox 镜像,但如果我们用 hello-world 镜像来使用相同的启动命令,会报错: 132 | 133 | ```shell 134 | wuxianmimi ~ % docker run hello-world ls 135 | docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "ls": executable file not found in $PATH: unknown. 136 | wuxianmimi ~ % 137 | ``` 138 | 139 | 原因就是 hello-world 镜像中的文件系统快照,并没有执行 ls 的程序。我们传递所有启动命令,必须得在镜像的文件系统快照中存在对应的程序才行。 140 | 141 | ⚠️:docker run = docker create + docker start 142 | 143 | - **docker ps** 144 | 145 | **docker ps** 的格式如下: 146 | 147 | ```shell 148 | docker ps [OPTIONS] 149 | ``` 150 | 151 | 这个命令可以输出计算机上所有正在运行的容器列表,先前测试的两个容器都是立即执行并退出的,所以我们得到列表为空: 152 | 153 | ```shell 154 | wuxianmimi ~ % docker ps 155 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 156 | wuxianmimi ~ % 157 | ``` 158 | 159 | 为了测试 **docker ps**,我们可以先在终端输入命令: 160 | 161 | ```shell 162 | docker run busybox ping bilibili.com 163 | ``` 164 | 165 | 然后新建终端窗口,输入 **docker ps**,会得到以下结果: 166 | 167 | ```shell 168 | wuxianmimi ~ % docker ps 169 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 170 | 35baf22ff8c1 busybox "ping bilibili.com" 7 seconds ago Up 6 seconds hardcore_chatelet 171 | wuxianmimi ~ % 172 | ``` 173 | 174 | 如果要查看所有的容器,而不只是正在运行的,可以添加 --**all** 选项: 175 | 176 | ```shell 177 | docker ps --all 178 | ``` 179 | 180 | - **docker start** 181 | 182 | 使用 **docker ps --all** 列出所有容器后,我们发现之前运行 hello-world 的容器目前处在 Exited 状态: 183 | 184 | ```shell 185 | wuxianmimi ~ % docker ps --all 186 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 187 | cc5bff91b03c hello-world "/hello" 3 seconds ago Exited (0) 2 seconds ago pedantic_hugle 188 | wuxianmimi ~ % 189 | ``` 190 | 191 | 在 Exited 状态并不代表容器没用了,我们可以使用 **docker start** 命令再次运行容器: 192 | 193 | ```shell 194 | docker start -a cc5bff91b03c 195 | ``` 196 | 197 | “cc5bff91b03c”是容器的 CONTAINER ID,选项 **-a** 来获取容器的标准输出/标准错误。 198 | 199 | **docker start** 的命令格式: 200 | 201 | ```shell 202 | docker start [OPTIONS] CONTAINER [CONTAINER...] 203 | ``` 204 | 205 | - **docker system prune** 206 | 207 | 这个命令会一口气删除所有未运行的容器来节省空间,同时也会删除本地的镜像缓存。 208 | 209 | - **docker logs** 210 | 211 | 如果我们在启动容器(**docker start**)时,忘记添加 **-a** 选项怎么办?我们就不能获得容器的标准输出了。 212 | 213 | docker logs 命令就是可以在不重新启动容器的情况下,获取容器的标准输出。 214 | 215 | 命令格式: 216 | 217 | ```shell 218 | docker logs [OPTIONS] CONTAINER 219 | ``` 220 | 221 | 示例: 222 | 223 | ```shell 224 | wuxianmimi ~ % docker create busybox echo hi mimi 225 | 6158cf3c32a190ff0bcb946d63317960269adbe70e71d8480022c82c0897ce74 226 | wuxianmimi ~ % docker start 6158cf3c32a190ff0bcb946d63317960269adbe70e71d8480022c82c0897ce74 227 | 6158cf3c32a190ff0bcb946d63317960269adbe70e71d8480022c82c0897ce74 228 | wuxianmimi ~ % docker ps --all 229 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 230 | 6158cf3c32a1 busybox "echo hi mimi" 22 seconds ago Exited (0) 8 seconds ago affectionate_dirac 231 | wuxianmimi ~ % docker logs 6158cf3c32a1 232 | hi mimi 233 | wuxianmimi ~ % 234 | ``` 235 | 236 | - **停止容器** 237 | 238 | 有两种方法可以停止容器,**docker stop** 和 **docker kill**。 239 | 240 | **docker stop** 发送 SIGTERM 信号给容器,容器接收到信号后,执行相关逻辑并停止。 241 | 242 | ```shell 243 | docker stop [OPTIONS] CONTAINER [CONTAINER...] 244 | ``` 245 | 246 | **docker kill** 发送 SIGKILL 信号给容器,使它立即终止。 247 | 248 | ```shell 249 | docker kill [OPTIONS] CONTAINER [CONTAINER...] 250 | ``` 251 | 252 | 一般情况下我们使用 **docker stop** 来停止容器,对于容器已经未响应的情况,再使用 **docker kill** 命令。不过默认情况下,**docker stop** 命令会在信号发送后 10 秒钟后,对未停止的容器再发送 SIGKILL 信号。 253 | 254 | - **docker exec** 255 | 256 | 课程中通过 redis 演示,在安装了 redis 的机器上运行 redis-server 启动服务,然后在另一个终端输入 redis-cli 来连接 redis 服务。 257 | 258 | 然而用 **docker run redis** 启动 redis-server 后,我们如何连接这个服务? 259 | 260 | 这就要使用到 **docker exec**: 261 | 262 | ```shell 263 | docker exec [OPTIONS] CONTAINER COMMAND [ARG...] 264 | ``` 265 | 266 | 这个命令使得我们可以在运行中的容器内执行命令: 267 | 268 | ```shell 269 | wuxianmimi ~ % docker ps --all 270 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 271 | 19b3b19d3225 redis "docker-entrypoint.s…" About a minute ago Exited (1) 13 seconds ago vigorous_williams 272 | a9cd082a2d4c busybox "ping bilibili.com" 9 minutes ago Exited (137) 9 minutes ago heuristic_hopper 273 | 6158cf3c32a1 busybox "echo hi mimi" 23 minutes ago Exited (0) 23 minutes ago affectionate_dirac 274 | wuxianmimi ~ % docker start 19b3b19d3225 275 | 19b3b19d3225 276 | wuxianmimi ~ % docker exec -it 19b3b19d3225 redis-cli 277 | 127.0.0.1:6379> set myname mimi 278 | OK 279 | 127.0.0.1:6379> get myname 280 | "mimi" 281 | 127.0.0.1:6379> 282 | ``` 283 | 284 | 在上面的例子中,我们先启动了 redis 容器,然后通过命令 **docker exec -it [容器 ID] redis-cli** 成功在容器内执行了命令,连接到 redis-server 中。 285 | 286 | 命令中的 **-it** 有什么作用? 287 | 288 | **-it** 是简写,相当于 **-i -t**。**-i**(-interactive) 选项使得我们终端保持连接到容器的标准输入,如果不添加的话,我们无法执行 redis-cli 连接成功后的操作。而 **-t**(-tty) 选项,可以理解为对容器 3 个标准的文件描述符的格式化,使得用户交互更方便。 289 | 290 | 不加 **-it** 选项执行 **docker exec** 命令,终端上仍旧没地方输入: 291 | 292 | ```shell 293 | wuxianmimi ~ % docker exec 19b3b19d3225 redis-cli 294 | wuxianmimi ~ % 295 | ``` 296 | 297 | 只加 **-i** 选项执行 **docker exec** 命令,输入命令时没了提示,且输出的格式没之前好看: 298 | 299 | ```shell 300 | wuxianmimi ~ % docker exec -i 19b3b19d3225 redis-cli 301 | set myname mimi 302 | OK 303 | get myname 304 | mimi 305 | ``` 306 | 307 | 如果我们要在容器内执行很多命令,那么一直反复的敲 **docker exec** 我们也手累,这时候可以输入: 308 | 309 | ```shell 310 | docker exec -it 19b3b19d3225 sh 311 | ``` 312 | 313 | 我们会在容器内打开 sh,sh 是 shell,可以让我们可以在容器内执行命令(相当于进入了一台 Linux 虚拟机) 314 | 315 | ```shell 316 | wuxianmimi ~ % docker exec -it 19b3b19d3225 sh 317 | # cd / 318 | # ls 319 | bin boot data dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var 320 | # redis-cli 321 | 127.0.0.1:6379> 322 | # 323 | ``` 324 | 325 | 需要退出时,使用 Ctrl+d 快捷键。 326 | 327 | ⚠️:docker run 也可以添加 -it 选项 328 | 329 | # 第三章:了解 Dockerfile 330 | 331 | 基本上围绕着如何书写 Dockerfile,以实现一个 redis-server 为例。 332 | 333 | - **Dockerfile** 334 | 335 | 我们创建自定义镜像从 Dockerfile 开始。 336 | 337 | Dockerfile 是一个文本文件,在里面添加我们的配置信息,这些信息定义了容器的行为方式。 338 | 339 | Dockerfile 文件经由 Docker Client 传给 Docker Server,最后由 Docker Server 解析并构建镜像。 340 | 341 | Dockerfile 中添加的配置项一般是三步曲(工作流) 342 | 343 | 1. 规定**基础镜像** 344 | 2. 运行命令来**安装**必要的依赖、程序 345 | 3. 规定容器**启动参数** 346 | 347 | 创建一个文件夹,并在文件夹内创建 Dockerfile 文件,内容如下: 348 | 349 | ```shell 350 | # 三步曲1:规定基础镜像 351 | # 基础镜像可以理解为操作系统,我们在安装其他程序前,需要先安装操作系统 352 | FROM alpine 353 | 354 | # 三步曲2:运行命令来安装必要的依赖、程序 355 | # apk 是 alpine 内建的包管理命令,我们使用这个包管理程序安装 redis 356 | RUN apk add --update redis 357 | 358 | # 三步曲3:规定容器启动参数 359 | CMD ["redis-server"] 360 | ``` 361 | 362 | 在文件夹根目录内,使用 **docker build .** 命令(不要漏了最后的 .): 363 | 364 | ```shell 365 | wuxianmimi docker-redis-image % docker build . 366 | [+] Building 15.5s (7/7) FINISHED 367 | => [internal] load build definition from Dockerfile 0.0s 368 | => => transferring dockerfile: 240B 0.0s 369 | => [internal] load .dockerignore 0.0s 370 | => => transferring context: 2B 0.0s 371 | => [internal] load metadata for docker.io/library/alpine:latest 5.1s 372 | => [1/2] FROM docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 5.8s 373 | => => resolve docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 0.0s 374 | => => sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 1.64kB / 1.64kB 0.0s 375 | => => sha256:af06af3514c44a964d3b905b498cf6493db8f1cde7c10e078213a89c87308ba0 528B / 528B 0.0s 376 | => => sha256:d3156fec8bcbc7b491a4edc271a7734dcfa186fc73282d4e120eeaaf2ce95c43 1.49kB / 1.49kB 0.0s 377 | => => sha256:261da4162673b93e5c0e7700a3718d40bcc086dbf24b1ec9b54bca0b82300626 3.26MB / 3.26MB 5.6s 378 | => => extracting sha256:261da4162673b93e5c0e7700a3718d40bcc086dbf24b1ec9b54bca0b82300626 0.1s 379 | => [auth] library/alpine:pull token for registry-1.docker.io 0.0s 380 | => [2/2] RUN apk add --update redis 4.6s 381 | => exporting to image 0.0s 382 | => => exporting layers 0.0s 383 | => => writing image sha256:5159f921568f4b843c002a76d29c37ca0ffbef1d284352a7b50691dd29d3fe29 0.0s 384 | 385 | Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 386 | ``` 387 | 388 | 5159f921568f4b843c002a76d29c37ca0ffbef1d284352a7b50691dd29d3fe29,这一串就是我们镜像的 ID。 389 | 390 | Dockerfile 中的每条指令都会“返回”一个镜像,在基于这个镜像创建的临时容器中执行下一条指令,临时容器在指令执行完毕后会被删除,而这个容器的文件系统快照会被保存到当前指令“返回”的镜像中。 391 | 392 | 使用 **docker run** 命令创建并运行容器: 393 | 394 | ```shell 395 | wuxianmimi docker-redis-image % docker run 5159f921568f4b843c002a76d29c37ca0ffbef1d284352a7b50691dd29d3fe29 396 | 1:C 07 Jan 2023 13:29:48.051 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 397 | 1:C 07 Jan 2023 13:29:48.051 # Redis version=7.0.7, bits=64, commit=00000000, modified=0, pid=1, just started 398 | 1:C 07 Jan 2023 13:29:48.051 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 399 | 1:M 07 Jan 2023 13:29:48.052 * monotonic clock: POSIX clock_gettime 400 | 1:M 07 Jan 2023 13:29:48.052 * Running mode=standalone, port=6379. 401 | 1:M 07 Jan 2023 13:29:48.052 # Server initialized 402 | 1:M 07 Jan 2023 13:29:48.054 * Ready to accept connections 403 | ``` 404 | 405 | 到这步,我们创建的第一个镜像就顺利完工了~~ 406 | 407 | 但是现在有个问题,我们每次运行都要粘贴这一长串镜像 ID 太麻烦了,有没有什么好办法解决这个问题? 408 | 409 | 答案就是在 **docker build** 命令增加 **-t** 选项来给我们自定义镜像加标签: 410 | 411 | ```shell 412 | wuxianmimi docker-redis-image % docker build -t wuxianmimi/redis:latest . 413 | [+] Building 1.6s (6/6) FINISHED 414 | => [internal] load build definition from Dockerfile 0.0s 415 | => => transferring dockerfile: 37B 0.0s 416 | => [internal] load .dockerignore 0.0s 417 | => => transferring context: 2B 0.0s 418 | => [internal] load metadata for docker.io/library/alpine:latest 1.5s 419 | => [1/2] FROM docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 0.0s 420 | => CACHED [2/2] RUN apk add --update redis 0.0s 421 | => exporting to image 0.0s 422 | => => exporting layers 0.0s 423 | => => writing image sha256:5159f921568f4b843c002a76d29c37ca0ffbef1d284352a7b50691dd29d3fe29 0.0s 424 | => => naming to docker.io/wuxianmimi/redis:latest 0.0s 425 | ``` 426 | 427 | 标签的约定格式就是: **[你的 DockerID]/[镜像名]:[版本号]** 428 | 429 | # 第四章:构建自定义镜像(NodeJS Demo) 430 | 431 | 这一章演示了如何将一个 NodeJS Web 应用打包成镜像,并使用该镜像运行容器,最后可以在浏览器访问这个 Web 应用。 432 | 433 | 新建文件夹,并在文件夹内创建两个文件: 434 | 435 | package.json 436 | 437 | ```javascript 438 | { 439 | "name": "docker-simpleweb", 440 | "version": "1.0.0", 441 | "description": "", 442 | "main": "index.js", 443 | "scripts": { 444 | "test": "echo \"Error: no test specified\" && exit 1", 445 | "start": "node index.js" 446 | }, 447 | "keywords": [], 448 | "author": "wuxianmimi", 449 | "license": "ISC", 450 | "dependencies": { 451 | "express": "^4.18.2" 452 | } 453 | } 454 | ``` 455 | 456 | index.js 457 | 458 | ```javascript 459 | const express = require("express"); 460 | 461 | const app = express(); 462 | 463 | app.get("/", (req, res) => { 464 | res.send("你好鸭"); 465 | }); 466 | 467 | app.listen(8080, () => { 468 | console.log("已监听8080端口"); 469 | }); 470 | ``` 471 | 472 | - **基础镜像问题** 473 | 474 | 创建 Dockerfile 文件,并增加 3 条指令: 475 | 476 | ```shell 477 | # 三步曲1:规定基础镜像 478 | FROM alpine 479 | 480 | # 三步曲2:运行命令来安装必要的依赖、程序 481 | RUN npm install 482 | 483 | # 三步曲3:规定容器启动参数 484 | CMD ["npm", "start"] 485 | ``` 486 | 487 | 然后运行 **docker build .** 命令,但是我们在第二步安装依赖时会报错: 488 | 489 | ```shell 490 | wuxianmimi docker-simpleweb % docker build . 491 | [+] Building 2.3s (5/5) FINISHED 492 | => [internal] load build definition from Dockerfile 0.0s 493 | => => transferring dockerfile: 229B 0.0s 494 | => [internal] load .dockerignore 0.0s 495 | => => transferring context: 2B 0.0s 496 | => [internal] load metadata for docker.io/library/alpine:latest 2.1s 497 | => CACHED [1/2] FROM docker.io/library/alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 0.0s 498 | => ERROR [2/2] RUN npm install 0.2s 499 | ------ 500 | > [2/2] RUN npm install: 501 | #5 0.172 /bin/sh: npm: not found 502 | ------ 503 | executor failed running [/bin/sh -c npm install]: exit code: 127 504 | ``` 505 | 506 | 报错说没有找到 npm 这个命令,原因就是 alpine 这个镜像中,并没有 NodeJS 环境,当然也不能运行 npm 命令。 507 | 508 | 我们当然可以在使用 apt 自己安装 NodeJS 环境,但是 Docker 官方提供了 Node 基础镜像,镜像中已经安装了 Node 开发需要的工具,所以我们直接使用即可。 509 | 510 | Docker Hub 上 Node 镜像地址:https://hub.docker.com/_/node 511 | 512 | 在 Supported tags and respective Dockerfile links 一栏下发现有很多支持的镜像标签,这里咪咪建议使用 14-alpine 这个标签,原因是 NodeJs 16 版本改动较大,可能与一些框架的老版本存在兼容性问题。 513 | 514 | 视频中讲师使用的 NodeJs 大版本是 8,喜欢原汁原味的同学可以使用这个版本。不过这个版本咪咪没有测试过,不一定能走通后续的流程。 515 | 516 | ```shell 517 | # 三步曲1:规定基础镜像 518 | # alpine ===> node:14-alpine 519 | FROM node:14-alpine 520 | 521 | # 三步曲2:运行命令来安装必要的依赖、程序 522 | RUN npm install 523 | 524 | # 三步曲3:规定容器启动参数 525 | CMD ["npm", "start"] 526 | ``` 527 | 528 | 运行 **docker build .** 529 | 530 | ```shell 531 | wuxianmimi docker-simpleweb % docker build . 532 | [+] Building 18.2s (6/6) FINISHED 533 | => [internal] load build definition from Dockerfile 0.0s 534 | => => transferring dockerfile: 237B 0.0s 535 | => [internal] load .dockerignore 0.0s 536 | => => transferring context: 2B 0.0s 537 | => [internal] load metadata for docker.io/library/node:14-alpine 3.5s 538 | => [1/2] FROM docker.io/library/node:14-alpine@sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a 13.0s 539 | => => resolve docker.io/library/node:14-alpine@sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a 0.0s 540 | => => sha256:f19b50748cdfe11c52ec028226d21a0cbc5a6b860e311b41a6299c2c43d1bfae 37.78MB / 37.78MB 12.1s 541 | => => sha256:7bfe7e7dc195d0082ab6b8c2bb09a8a405c51369b5c25c015327227f1390a312 2.43MB / 2.43MB 1.5s 542 | => => sha256:cb8b674ae1bc8e69210b956081a7c092daabfd2a4834f660ef3e0cadaab5db44 449B / 449B 1.9s 543 | => => sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a 1.43kB / 1.43kB 0.0s 544 | => => sha256:98f1a5d744f2343703913c3981098ac92fed74a6edec9133291d9f0ad4a03fe8 1.16kB / 1.16kB 0.0s 545 | => => sha256:8673fd44cb467741701c5006a1edca4f315bc1ac8af2964fe0a9586c7bbe195f 6.45kB / 6.45kB 0.0s 546 | => => extracting sha256:f19b50748cdfe11c52ec028226d21a0cbc5a6b860e311b41a6299c2c43d1bfae 0.7s 547 | => => extracting sha256:7bfe7e7dc195d0082ab6b8c2bb09a8a405c51369b5c25c015327227f1390a312 0.1s 548 | => => extracting sha256:cb8b674ae1bc8e69210b956081a7c092daabfd2a4834f660ef3e0cadaab5db44 0.0s 549 | => [2/2] RUN npm install 1.5s 550 | => exporting to image 0.0s 551 | => => exporting layers 0.0s 552 | => => writing image sha256:5a8723a245fdf92ef58101dd66e664c3365af9015b84adb30fc93a58dd25b07a 0.0s 553 | ``` 554 | 555 | 构建成功,我们运行一下 556 | 557 | ```shell 558 | wuxianmimi docker-simpleweb % docker run 5a8723a245fd 559 | npm ERR! code ENOENT 560 | npm ERR! syscall open 561 | npm ERR! path /package.json 562 | npm ERR! errno -2 563 | npm ERR! enoent ENOENT: no such file or directory, open '/package.json' 564 | npm ERR! enoent This is related to npm not being able to find a file. 565 | npm ERR! enoent 566 | 567 | npm ERR! A complete log of this run can be found in: 568 | npm ERR! /root/.npm/_logs/2023-01-09T02_53_07_209Z-debug.log 569 | ``` 570 | 571 | 报错了,说是没有 package.json 这个文件,什么情况?(可能由于 Docker Engine 版本差异,视频中讲师在构建阶段就会报着个错误) 572 | 573 | - **COPY 指令** 574 | 575 | 缺少 package.json 这个文件,是因为目前容器内的文件系统快照,是 node:14-alpine 这个镜像的拷贝,所以里面当然不会有我们的代码。 576 | 577 | 所以我们需要在构建镜像的时候,将我们的代码拷贝到容器内,这需要用到 **COPY** 指令: 578 | 579 | ```shell 580 | COPY [--chown=:] ... 581 | COPY [--chown=:] ["",... ""] 582 | ``` 583 | 584 | **COPY** 指令把 的文件和文件夹,拷贝到容器内的文件系统 中。 585 | 586 | 在我们 Dockerfile 中添加 **COPY** 指令: 587 | 588 | ```shell 589 | # 三步曲1:规定基础镜像 590 | FROM node:14-alpine 591 | 592 | # 三步曲2:运行命令来安装必要的依赖、程序 593 | COPY ./ ./ 594 | RUN npm install 595 | 596 | # 三步曲3:规定容器启动参数 597 | CMD ["npm", "start"] 598 | ``` 599 | 600 | 这里咪咪建议在项目根目录创建一个 .npmrc 文件,在文件内添加一行: 601 | 602 | ```shell 603 | registry=https://registry.npmmirror.com 604 | ``` 605 | 606 | 这是 npm 镜像地址,对大陆用户比较友好,否则在容器内安装 npm 依赖的时间很长很长。。。(后面所有 NodeJS 项目都一样) 607 | 608 | 重新构建运行容器: 609 | 610 | ```shell 611 | wuxianmimi docker-simpleweb % docker run 6c8d589ccc7e 612 | 613 | > docker-simpleweb@1.0.0 start / 614 | > node index.js 615 | 616 | 已监听8080端口 617 | ``` 618 | 619 | 服务成功运行了,我们打开本地浏览器,在地址栏输入 localhost:8080 并回车: 620 | 621 | ![img]() 622 | 623 | 无法访问应用 624 | 625 | 咦,网站无法访问,什么情况? 626 | 627 | - **端口映射** 628 | 629 | 当我们在本地浏览器前往 http://localhost:8080 时,请求的是本地 8080 端口,而不是容器内的 8080 端口。而我们本地并没有程序监听这个端口,所以也不会有响应。 630 | 631 | 默认情况下,容器内的端口是**不会\*\***接收\*\*到外部的请求的,我们需要在运行容器时,指定端口映射的规则。 632 | 633 | 在 **docker run** 命令添加 **-p** [本地端口]:[容器端口] 选项,增加端口映射: 634 | 635 | ```shell 636 | wuxianmimi docker-simpleweb % docker run -p 8080:8080 b964820c57bd7 637 | 638 | > docker-simpleweb@1.0.0 start / 639 | > node index.js 640 | 641 | 已监听8080端口 642 | ``` 643 | 644 | 重新在浏览器访问 localhost:8080,成功返回: 645 | 646 | ![img]() 647 | 648 | 获得返回数据 649 | 650 | - **工作目录** 651 | 652 | 我们目前的项目文件,都是通过 **COPY** 指令拷贝到容器内的根目录的,这样的做法其实不太好。原因是根目录还有很多其他的文件,可能会与我们的项目有冲突。 653 | 654 | ```shell 655 | / # ls 656 | Dockerfile home mnt package.json sbin usr 657 | bin index.js node_modules proc srv var 658 | dev lib opt root sys 659 | etc media package-lock.json run tmp 660 | ``` 661 | 662 | 解决办法就是在 Dockerfile 中使用 **WORKDIR** 指令,这个指令设置一个文件夹,成为之后 **RUN**、**CMD**、**COPY** 这些指令的起始目录。 663 | 664 | 更新我们的 Dockerfile: 665 | 666 | ```shell 667 | # 三步曲1:规定基础镜像 668 | FROM node:14-alpine 669 | 670 | # 增加工作目录 671 | WORKDIR /usr/app 672 | 673 | # 三步曲2:运行命令来安装必要的依赖、程序 674 | COPY ./ ./ 675 | RUN npm install 676 | 677 | # 三步曲3:规定容器启动参数 678 | CMD ["npm", "start"] 679 | ``` 680 | 681 | 重新构建镜像并运行,现在在容器中的项目文件结构: 682 | 683 | ```shell 684 | /usr/app # ls 685 | Dockerfile index.js node_modules package-lock.json package.json 686 | /usr/app # cd / 687 | / # ls 688 | bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var 689 | / # 690 | ``` 691 | 692 | - **优化构建** 693 | 694 | 目前构建镜像的流程已经走通了,但是仍有一个细节可以优化。 695 | 696 | 虽然 **docker build** 每一步指令都有缓存机制,只要上一步指令不更改,当前指令就会使用缓存,节省时间。但是如果我们现在更改了 index.js 的内容,我们肯定需要重新构建镜像,由于 **RUN** npm install 这步之前的指令肯定会变动(因为我们改了代码),所以每次更改代码后构建都需要重新安装依赖,比较浪费时间。 697 | 698 | 于是我们可以分两步 **COPY** 文件,先将 package.json 拷贝进容器,执行安装依赖的指令,再将其余文件拷贝进容器。这样的话,只要 package.json 不变,安装依赖这一步就可以用缓存的镜像。 699 | 700 | ```shell 701 | # 三步曲1:规定基础镜像 702 | FROM node:14-alpine 703 | 704 | # 增加工作目录 705 | WORKDIR /usr/app 706 | 707 | # 三步曲2:运行命令来安装必要的依赖、程序 708 | COPY ./package.json ./ 709 | COPY ./.npmrc ./ 710 | RUN npm install 711 | COPY ./ ./ 712 | 713 | # 三步曲3:规定容器启动参数 714 | CMD ["npm", "start"] 715 | ``` 716 | 717 | # 第五章:了解 Docker Compose 718 | 719 | 这一章要实现一个应用,当用户访问页面时,返回当前页面访问总次数。 720 | 721 | - **准备项目** 722 | 723 | 新建文件夹,并在文件夹内创建: 724 | 725 | package.json 726 | 727 | ```javascript 728 | { 729 | "name": "docker-visists", 730 | "version": "1.0.0", 731 | "description": "", 732 | "main": "index.js", 733 | "scripts": { 734 | "test": "echo \"Error: no test specified\" && exit 1", 735 | "start": "node index.js" 736 | }, 737 | "keywords": [], 738 | "author": "", 739 | "license": "ISC", 740 | "dependencies": { 741 | "express": "*", 742 | "redis": "2.8.0" 743 | } 744 | } 745 | ``` 746 | 747 | index.js 748 | 749 | ```javascript 750 | const express = require("express"); 751 | const redis = require("redis"); 752 | 753 | const app = express(); 754 | const client = redis.createClient(); 755 | client.set("visits", 0); 756 | 757 | app.get("/", (req, res) => { 758 | client.get("visits", (err, visits) => { 759 | res.send("访问次数 " + visits); 760 | client.set("visits", parseInt(visits) + 1); 761 | }); 762 | }); 763 | 764 | app.listen(8081, () => { 765 | console.log("监听8081端口中..."); 766 | }); 767 | ``` 768 | 769 | Dockerfile 770 | 771 | ```shell 772 | FROM node:14-alpine 773 | 774 | WORKDIR '/app' 775 | 776 | COPY package.json . 777 | RUN npm install 778 | COPY . . 779 | 780 | CMD ["npm", "start"] 781 | ``` 782 | 783 | .npmrc(额外加的,提高 npm 依赖安装速度) 784 | 785 | ```shell 786 | registry=https://registry.npmmirror.com 787 | ``` 788 | 789 | 然后运行 **docker build** 命令打包镜像: 790 | 791 | ```shell 792 | wuxianmimi docker-visists % docker build -t wuxianmimi/visits . 793 | [+] Building 4.3s (11/11) FINISHED 794 | => [internal] load build definition from Dockerfile 0.0s 795 | => => transferring dockerfile: 159B 0.0s 796 | => [internal] load .dockerignore 0.0s 797 | => => transferring context: 2B 0.0s 798 | => [internal] load metadata for docker.io/library/node:14-alpine 1.7s 799 | => [internal] load build context 0.0s 800 | => => transferring context: 507B 0.0s 801 | => [1/6] FROM docker.io/library/node:14-alpine@sha256:80e825b1f5ab859498a2f0f98f8197131a562906e5f8c95977057502e68ca05a 0.0s 802 | => CACHED [2/6] WORKDIR /app 0.0s 803 | => [3/6] COPY .npmrc . 0.0s 804 | => [4/6] COPY package.json . 0.0s 805 | => [5/6] RUN npm install 2.3s 806 | => [6/6] COPY . . 0.0s 807 | => exporting to image 0.1s 808 | => => exporting layers 0.1s 809 | => => writing image sha256:e5d5a301d20e68b4ebc59d451410ebbe9379bd2c3519acea6f1f3b6b12a9bfed 0.0s 810 | => => naming to docker.io/wuxianmimi/visits 0.0s 811 | ``` 812 | 813 | - **Docker Compose** 814 | 815 | Docker Compose 是 Docker 安装时一起安装的一个命令行工具,作用是使同时启动多个容器更方便。 816 | 817 | 我们通过 **docker-compose.yml** 文件来配置相关信息,在这个项目中,我们需要启动两个容器,一个是 redis,另一个是我们的 Node 应用。 818 | 819 | docker-compose.yml 820 | 821 | ```yaml 822 | # 使用的的 docker compose 文件标准 823 | version: '3' 824 | # 服务列表,一个容器可以理解为一个服务 825 | services: 826 | # 容器1 827 | redis-server: 828 | # 使用的镜像 829 | image: 'redis' 830 | # 容器2 831 | node-app: 832 | # 构建当前项目 833 | build: . 834 | # 端口映射 835 | ports: 836 | - "8081:8081" 837 | ``` 838 | 839 | services 列表中的容器,在网络层面是自动互通的,我们只需要在 Node 应用中,指明 redis 连接的 host 为 redis-server 即可,docker 会自动帮我们转发至对应名称的容器中。 840 | 841 | - **启动** 842 | 843 | 通过命令 **docker-compose up** 来启动容器: 844 | 845 | ```shell 846 | wuxianmimi docker-visists % docker-compose up 847 | [+] Running 2/0 848 | ⠿ Container docker-visists-node-app-1 Created 0.0s 849 | ⠿ Container docker-visists-redis-server-1 Created 0.0s 850 | Attaching to docker-visists-node-app-1, docker-visists-redis-server-1 851 | docker-visists-redis-server-1 | 1:C 09 Jan 2023 13:17:30.111 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 852 | docker-visists-redis-server-1 | 1:C 09 Jan 2023 13:17:30.111 # Redis version=7.0.7, bits=64, commit=00000000, modified=0, pid=1, just started 853 | docker-visists-redis-server-1 | 1:C 09 Jan 2023 13:17:30.111 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 854 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.111 * monotonic clock: POSIX clock_gettime 855 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.112 * Running mode=standalone, port=6379. 856 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.112 # Server initialized 857 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * Loading RDB produced by version 7.0.7 858 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * RDB age 3 seconds 859 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * RDB memory usage when created 0.92 Mb 860 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * Done loading RDB, keys loaded: 1, keys expired: 0. 861 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * DB loaded from disk: 0.000 seconds 862 | docker-visists-redis-server-1 | 1:M 09 Jan 2023 13:17:30.114 * Ready to accept connections 863 | docker-visists-node-app-1 | 864 | docker-visists-node-app-1 | > docker-visists@1.0.0 start /app 865 | docker-visists-node-app-1 | > node index.js 866 | docker-visists-node-app-1 | 867 | docker-visists-node-app-1 | 监听8081端口中... 868 | ``` 869 | 870 | 浏览器访问 localhost:8081 页面: 871 | 872 | ![img]() 873 | 874 | node-app 应用 875 | 876 | 需要停止时,通过 **docker-compose down** 来停止容器并移除容器与网络: 877 | 878 | ```shell 879 | wuxianmimi docker-visists % docker-compose down 880 | [+] Running 3/0 881 | ⠿ Container docker-visists-node-app-1 Removed 0.0s 882 | ⠿ Container docker-visists-redis-server-1 Removed 0.0s 883 | ⠿ Network docker-visists_default Removed 0.1s 884 | ``` 885 | 886 | - **自动重启容器** 887 | 888 | 目前为止,如果我们的应用发生错误了,容器就停止了,那如何让容器关闭时自动重启呢? 889 | 890 | 可以在 docker-compose.yml 中配置 **restart** 属性: 891 | 892 | ```yaml 893 | # 使用的的 docker compose 文件标准 894 | version: "3" 895 | # 服务列表,一个容器可以理解为一个服务 896 | services: 897 | # 容器1 898 | redis-server: 899 | # 使用的镜像 900 | image: "redis" 901 | # 容器2 902 | node-app: 903 | # 程序永远会在停止时自动重启 904 | restart: always 905 | # 构建当前项目 906 | build: . 907 | # 端口映射 908 | ports: 909 | - "8081:8081" 910 | ``` 911 | 912 | restart 一共有四种策略 913 | 914 | ![img]() 915 | 916 | restart 策略 917 | 918 | - **docker-compose ps** 919 | 920 | 与 **docker ps** 类似,**docker-compose ps** 可以查看容器的状态(不只是正在运行的): 921 | 922 | ```shell 923 | wuxianmimi docker-visists % docker-compose ps 924 | NAME COMMAND SERVICE STATUS PORTS 925 | docker-visists-node-app-1 "docker-entrypoint.s…" node-app exited (0) 926 | docker-visists-redis-server-1 "docker-entrypoint.s…" redis-server exited (0) 927 | ``` 928 | 929 | ⚠️:与 docker 命令不同,docker-compose 命令,都需要目录内有 docker-compose.yml 文件,否则会报错。 930 | 931 | # 第六章:构建自定义镜像+(Nginx + React) 932 | 933 | 接下来两章讲解 Docker 开发的工作流,了解 Docker 在开发、测试、部署、开发循环流程中扮演的角色: 934 | 935 | 开发:介绍了如何使用 Github 管理代码 936 | 937 | 测试:使用 Travis CI 进行持续集成 938 | 939 | 部署:在亚马逊 AWS 上部署 940 | 941 | - **初始化** 942 | 943 | 使用 **npx create-react-app docker-react-frontend** 初始化前端项目。(过程可能有点长,可增加选项跳过安装步骤) 944 | 945 | 添加 .npmrc 文件。 946 | 947 | 添加 Dockerfile.dev 文件,开发环境的构建镜像配置: 948 | 949 | ```shell 950 | FROM node:14-alpine 951 | 952 | WORKDIR '/usr/app' 953 | 954 | COPY .npmrc . 955 | COPY package.json . 956 | RUN npm install 957 | COPY . . 958 | 959 | CMD ["npm", "run", "start"] 960 | ``` 961 | 962 | 这一章区分开发环境和生产环境,项目中 Dockerfile 是区分的,使用 Dockerfile.dev 配置开发环境,构建时添加 -f 选项指明配置文件:docker build -f Dockerfile.dev . 963 | 964 | - **Volumes** 965 | 966 | 视频中在前端项目时,如果我们改动代码,不会影响容器中的代码,容器内使用还是构建时的代码,而应用又是在容器内启动的,所以也不会有热更新。 967 | 968 | 而我们不可能改一次代码就构建一次镜像,那样的话太麻烦了,还有没有其他什么方法? 969 | 970 | 答案就是要用 Docker 中 Volumes 这个机制。 971 | 972 | Volumes 可能理解起来比较抽象,可以先简单的等同端口映射,我们将本地的文件“映射”到容器内部。 973 | 974 | 实际做法是 docker run 命令增加 -v 选项: 975 | 976 | ```shell 977 | wuxianmimi docker-react-frontend % docker run -p 3000:3000 -v /usr/app/node_modules -v $(pwd):/usr/app 72f579b2bf49e403 978 | 979 | > docker-react-frontend@0.1.0 start /usr/app 980 | > react-scripts start 981 | 982 | (node:25) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option. 983 | (Use `node --trace-deprecation ...` to show where the warning was created) 984 | (node:25) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option. 985 | Starting the development server... 986 | 987 | Compiled successfully! 988 | 989 | You can now view docker-react-frontend in the browser. 990 | 991 | Local: http://localhost:3000 992 | On Your Network: http://172.17.0.2:3000 993 | 994 | Note that the development build is not optimized. 995 | To create a production build, use npm run build. 996 | 997 | webpack compiled successfully 998 | Compiling... 999 | Compiled successfully! 1000 | webpack compiled successfully 1001 | ``` 1002 | 1003 | 第一个 **-v /usr/app/node_modules**,没有冒号,表示容器内这个文件夹不需要映射,使用容器本身的文件。 1004 | 1005 | 第二个 **-v $(pwd):/usr/app**,在有冒号的情况下,左边的映射到右边。冒号左边表示本地目录,冒号右边表示容器内目录。 1006 | 1007 | 以上操作也可以使用 docker-compose 来简化,添加 docker-compose.yml 文件: 1008 | 1009 | ```yaml 1010 | version: "3" 1011 | services: 1012 | web: 1013 | # 这里同之前写法不一样,因为默认是找 Dockerfile 文件的 1014 | # 由于我们的文件名不一样,所以要手动配置 1015 | build: 1016 | context: . 1017 | dockerfile: Dockerfile.dev 1018 | ports: 1019 | - "3000:3000" 1020 | volumes: 1021 | - /usr/app/node_modules 1022 | - .:/usr/app 1023 | ``` 1024 | 1025 | 运行 **docker-compose up** 命令,顺利启动。 1026 | 1027 | - **测试** 1028 | 1029 | docker-compose.yml 增加第二个服务,用来测试应用: 1030 | 1031 | ```yaml 1032 | version: "3" 1033 | services: 1034 | web: 1035 | # 这里同之前写法不一样,因为默认是找 Dockerfile 文件的 1036 | # 由于我们的文件名不一样,所以要手动配置 1037 | build: 1038 | context: . 1039 | dockerfile: Dockerfile.dev 1040 | ports: 1041 | - "3000:3000" 1042 | volumes: 1043 | - /usr/app/node_modules 1044 | - .:/usr/app 1045 | tests: 1046 | build: 1047 | context: . 1048 | dockerfile: Dockerfile.dev 1049 | volumes: 1050 | - /usr/app/node_modules 1051 | - .:/usr/app 1052 | # 覆盖默认命令 1053 | command: ["npm", "run", "test"] 1054 | ``` 1055 | 1056 | 运行 **docker-compose up** 命令时,会自动运行测试服务。 1057 | 1058 | - **使用 Nginx 来做 web 服务器** 1059 | 1060 | 前端应用使用 npm run build 打包成静态文件后放在服务器上,然后请求通过 Nginx 访问这些文件。 1061 | 1062 | 这一步的难点有两个,第一,打包需要安装依赖,而依赖又很大占空间怎么办?第二,打包的文件和 Ngnix 如何结合? 1063 | 1064 | 创建 Dockerfile 文件: 1065 | 1066 | ```shell 1067 | # 给第一步构建阶段取别名 builder 1068 | FROM node:14-alpine as builder 1069 | WORKDIR '/usr/app' 1070 | COPY .npmrc . 1071 | COPY package.json . 1072 | RUN npm install 1073 | COPY . . 1074 | RUN ["npm", "run", "build"] 1075 | 1076 | # FROM 指令划分构建阶段,下面是第二步 1077 | FROM nginx 1078 | # 指明文件从 builder 阶段产生的镜像中来 1079 | COPY --from=builder /usr/app/build /usr/share/nginx/html 1080 | # nginx 镜像有默认启动命令,不需要我们设置 1081 | ``` 1082 | 1083 | 命令行输入 **docker build .** 构建,构建成功后输入 **docker run** 命令启动容器: 1084 | 1085 | ```shell 1086 | wuxianmimi docker-react-frontend % docker run -p 80:80 2d16f24ae5fbeefe00 1087 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration 1088 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ 1089 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 1090 | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 1091 | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf 1092 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh 1093 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh 1094 | /docker-entrypoint.sh: Configuration complete; ready for start up 1095 | 2023/01/10 07:03:18 [notice] 1#1: using the "epoll" event method 1096 | 2023/01/10 07:03:18 [notice] 1#1: nginx/1.23.3 1097 | 2023/01/10 07:03:18 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 1098 | 2023/01/10 07:03:18 [notice] 1#1: OS: Linux 5.15.49-linuxkit 1099 | 2023/01/10 07:03:18 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 1100 | 2023/01/10 07:03:18 [notice] 1#1: start worker processes 1101 | 2023/01/10 07:03:18 [notice] 1#1: start worker process 29 1102 | 2023/01/10 07:03:18 [notice] 1#1: start worker process 30 1103 | 2023/01/10 07:03:18 [notice] 1#1: start worker process 31 1104 | 2023/01/10 07:03:18 [notice] 1#1: start worker process 32 1105 | ``` 1106 | 1107 | 浏览器访问 http://localhost/ 1108 | 1109 | ![img]() 1110 | 1111 | React 项目 1112 | 1113 | # 1114 | 1115 | # 第七章:持续集成与部署(Travis CI + AWS Elastic Beanstalk) 1116 | 1117 | 这一章紧接上一章节,真正开始在 Github、Travis CI、AWS 上进行操作。 1118 | 1119 | 略。 1120 | 1121 | # 第八章:多容器应用:准备阶段(Nginx + React + Express + Postgres + Redis) 1122 | 1123 | 接下来将继续提高应用架构复杂度,更多容器情况下的开发流程。 1124 | 1125 | 容器 1:前端 react 客户端 1126 | 1127 | 容器 2:express web 服务器 1128 | 1129 | 容器 3:计算斐波那契数列的 worker 1130 | 1131 | 容器 4:Nginx 1132 | 1133 | 容器 5:redis 1134 | 1135 | 容器 6:Postgres 1136 | 1137 | 这个项目实现了以下效果,用户请求页面后,在页面可以输入一个数字发送给服务端,服务端响应请求并在 worker 中计算用户发来数字在斐波那契数列对应项的值。 1138 | 1139 | 页面上有存储在 Postgres 的历史输入值,还有存储在 redis 的计算结果。 1140 | 1141 | 这一章节都是在实现容器 1、容器 2、容器 3 这三个应用,如果只想关注在 Docker 技术的可略过。 1142 | 1143 | 这一章节完成后的代码模板,懒得自己敲的话可以用咪咪的: 1144 | 1145 | https://github.com/lyf61/Docker-and-Kubernetes-The-Complete-Guide-Complex-App-Starter 1146 | 1147 | # 第九章:多容器应用:与 Docker 结合 1148 | 1149 | 经过上一章后,我们项目目前文件结构如下: 1150 | 1151 | ![img]() 1152 | 1153 | 代码项目结构 1154 | 1155 | client 是 React 项目,server 是 Express 服务器,worker 是计算斐波那契数列的程序。 1156 | 1157 | 课程先从 client 开始,在 client 目录中创建 Dockerfile.dev: 1158 | 1159 | ```shell 1160 | FROM node:14-alpine 1161 | 1162 | WORKDIR '/app' 1163 | 1164 | # 不要忘了创建 .npmrc 来节省安装依赖时间 1165 | COPY ./.npmrc . 1166 | COPY ./package.json . 1167 | RUN npm install 1168 | COPY . . 1169 | 1170 | CMD ["npm", "run", "start"] 1171 | ``` 1172 | 1173 | server 与 worker 都是 NodeJS 项目,所以创建的 Dockerfile.dev 一样: 1174 | 1175 | ```shell 1176 | # 这里用Node12版本 1177 | # 原因是连接 postgres 数据库的 pg@7.4.3(视频中的版本)不支持Node14 1178 | # 否则会有BUG 1179 | FROM node:12-alpine 1180 | 1181 | WORKDIR '/app' 1182 | 1183 | COPY ./.npmrc . 1184 | COPY ./package.json . 1185 | RUN npm install 1186 | COPY . . 1187 | 1188 | # 与 client 唯一的不同 1189 | CMD ["npm", "run", "dev"] 1190 | ``` 1191 | 1192 | 然后在整个项目根目录增加 docker-compose.yaml: 1193 | 1194 | ```shell 1195 | version: '3' 1196 | services: 1197 | # postgres数据库 1198 | postgres: 1199 | image: 'postgres:10.5' 1200 | # redis数据库 1201 | redis: 1202 | image: 'redis:4-alpine' 1203 | # Express 服务器 1204 | api: 1205 | build: 1206 | dockerfile: Dockerfile.dev 1207 | context: ./server 1208 | volumes: 1209 | - /app/node_modules 1210 | - ./server:/app 1211 | # 通过 environment 设置容器的**运行时**环境变量 1212 | environment: 1213 | - REDIS_HOST=redis 1214 | - REDIS_PORT=6379 1215 | - PGUSER=postgres 1216 | - PGHOST=postgres 1217 | - PGDATABASE=postgres 1218 | - PGPASSWORD=postgres_password 1219 | - PGPORT=5432 1220 | # React 1221 | client: 1222 | build: 1223 | dockerfile: Dockerfile.dev 1224 | context: ./client 1225 | volumes: 1226 | - /app/node_modules 1227 | - ./client:/app 1228 | # 计算斐波那契数列程序 1229 | worker: 1230 | build: 1231 | dockerfile: Dockerfile.dev 1232 | context: ./worker 1233 | volumes: 1234 | - /app/node_modules 1235 | - ./worker:/app 1236 | environment: 1237 | - REDIS_HOST=redis 1238 | - REDIS_PORT=6379 1239 | ``` 1240 | 1241 | 相比之前的配置,这里新增了 **environment**,可以配置容器**运行时**的环境变量。 1242 | 1243 | - **Nginx** 1244 | 1245 | 根据一开始的架构设计,最终暴露出来的服务是 Nginx,请求经过 Nginx 处理后,转发至 React 侧或 Express 侧。 1246 | 1247 | 根目录新增 nginx 文件夹来存放 Nginx 项目配置,并在目录内增加两个文件: 1248 | 1249 | default.conf 1250 | 1251 | ```nginx 1252 | upstream client { 1253 | server client:3000; 1254 | } 1255 | 1256 | upstream api { 1257 | server api:5000; 1258 | } 1259 | 1260 | server { 1261 | listen 80; 1262 | 1263 | location / { 1264 | proxy_pass http://client; 1265 | } 1266 | 1267 | location /sockjs-node { 1268 | proxy_pass http://client; 1269 | proxy_http_version 1.1; 1270 | proxy_set_header Upgrade $http_upgrade; 1271 | proxy_set_header Connection "Upgrade"; 1272 | } 1273 | 1274 | location /api { 1275 | rewrite /api/(.*) /$1 break; 1276 | proxy_pass http://api; 1277 | } 1278 | } 1279 | ``` 1280 | 1281 | Dockerfile.dev 1282 | 1283 | ```shell 1284 | FROM nginx:1.15.2-alpine 1285 | 1286 | COPY ./default.conf /etc/nginx/conf.d/default.conf 1287 | ``` 1288 | 1289 | 同时在根目录的 docker-compose.yaml 中新增 Nginx 的配置,完整配置如下: 1290 | 1291 | ```yaml 1292 | version: "3" 1293 | services: 1294 | # postgres数据库 1295 | postgres: 1296 | image: "postgres:10.5" 1297 | # redis数据库 1298 | redis: 1299 | image: "redis:4-alpine" 1300 | # nginx 1301 | ngxin: 1302 | restart: always 1303 | build: 1304 | dockerfile: Dockerfile.dev 1305 | context: ./nginx 1306 | ports: 1307 | - "3030:80" 1308 | # Express 服务器 1309 | api: 1310 | build: 1311 | dockerfile: Dockerfile.dev 1312 | context: ./server 1313 | volumes: 1314 | - /app/node_modules 1315 | - ./server:/app 1316 | # 通过 environment 设置容器的**运行时**环境变量 1317 | environment: 1318 | - REDIS_HOST=redis 1319 | - REDIS_PORT=6379 1320 | - PGUSER=postgres 1321 | - PGHOST=postgres 1322 | - PGDATABASE=postgres 1323 | - PGPASSWORD=postgres_password 1324 | - PGPORT=5432 1325 | # React 1326 | client: 1327 | build: 1328 | dockerfile: Dockerfile.dev 1329 | context: ./client 1330 | volumes: 1331 | - /app/node_modules 1332 | - ./client:/app 1333 | # 计算斐波那契数列程序 1334 | worker: 1335 | build: 1336 | dockerfile: Dockerfile.dev 1337 | context: ./worker 1338 | volumes: 1339 | - /app/node_modules 1340 | - ./worker:/app 1341 | environment: 1342 | - REDIS_HOST=redis 1343 | - REDIS_PORT=6379 1344 | ``` 1345 | 1346 | 命令行输入 **docker-compose up —build** 来启动项目,在浏览器输入 http://localhost:3030/ 访问特面,跳出: 1347 | 1348 | ![img]() 1349 | 1350 | 启动项目成功 1351 | 1352 | # 第十章:多容器应用:持续集成 1353 | 1354 | 略。 1355 | 1356 | # 第十一章:多容器应用:部署至 AWS 1357 | 1358 | 略。 1359 | 1360 | # 第十二章:Kubernetes 介绍与安装 1361 | 1362 | 从这一章节开始正式进入 Kubernetes 的世界。 1363 | 1364 | - **Kubernetes 是什么?** 1365 | 1366 | 一套在多个机器上运行多个容器的系统。 1367 | 1368 | - **为什么用 Kubernetes?** 1369 | 1370 | 在容器很多时,使得管理容器和镜像更简单。 1371 | 1372 | - 一些概念 1373 | 1374 | 1. Nodes 1375 | 1376 | 节点,一个虚拟机或者物理机器。 1377 | 1378 | 2. Pod 1379 | 1380 | 一组容器,运行在节点中。Kubernetes 中管理的**最小计算单元**。 1381 | 1382 | 3. Kubernetes Clusters 1383 | 1384 | Kubernetes 集群,可以理解为 Master + Nodes。 1385 | 1386 | 4. Master 1387 | 1388 | 负责管理集群的一个虚拟机或者物理机器。 1389 | 1390 | 5. minikube 1391 | 1392 | 让我们在本地运行 Kubernetes 的命令行工具。 1393 | 1394 | 6. kubectl 1395 | 1396 | 与 Master 交互的命令行工具。 1397 | 1398 | - **安装** 1399 | 1400 | 现在安装 Docker 会自动安装上 kubectl,所以我们只要安装 minikube 就可以了。 1401 | 1402 | minikube 官方安装文档:https://minikube.sigs.k8s.io/docs/start/ 1403 | 1404 | 选择相应的版本安装完毕后即可。 1405 | 1406 | 安装完成后,使用 **minikube start** 命令来启动集群。 1407 | 1408 | 启动需要花点时间安装,启动后,可以通过 **minikube status** 和 **kubectl cluster-info** 这两个命令查看集群运行信息: 1409 | 1410 | ```shell 1411 | wuxianmimi docker-complex % minikube status 1412 | minikube 1413 | type: Control Plane 1414 | host: Running 1415 | kubelet: Running 1416 | apiserver: Running 1417 | kubeconfig: Configured 1418 | ``` 1419 | 1420 | **kubectl cluster-info** 1421 | 1422 | ```shell 1423 | wuxianmimi docker-complex % kubectl cluster-info 1424 | Kubernetes control plane is running at https://127.0.0.1:63259 1425 | CoreDNS is running at https://127.0.0.1:63259/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy 1426 | 1427 | To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 1428 | ``` 1429 | 1430 | 创建文件夹 kubernetes-simplek8s,并在文件夹内创建 2 个文件: 1431 | 1432 | client-pod.yaml 1433 | 1434 | ```yaml 1435 | # Kubernetes API 的版本 1436 | apiVersion: v1 1437 | # 这个配置文件生成的对象类型 1438 | # Pod 就是一个或一组容器 1439 | kind: Pod 1440 | # 唯一标识对象的一些数据 1441 | metadata: 1442 | name: client-pod 1443 | labels: 1444 | component: web 1445 | # 对象的状态 1446 | spec: 1447 | containers: 1448 | # 容器名,任意取 1449 | - name: client 1450 | # 使用的镜像,镜像必须发布在 Docker Hub 上才能用 1451 | image: stephengrider/multi-client 1452 | # 暴露的端口 1453 | ports: 1454 | - containerPort: 3000 1455 | ``` 1456 | 1457 | client-node-port.yaml 1458 | 1459 | ```yaml 1460 | apiVersion: v1 1461 | # Service 类型,设置集群中的网络 1462 | kind: Service 1463 | metadata: 1464 | name: client-node-port 1465 | spec: 1466 | # 子类型,一共有四种取值: 1467 | # 1.ClusterIP,默认值,暴露内部IP,使得集群内部其他对象可以访问 1468 | # 2.NodePort,暴露内部IP+端口,仅用于开发目的 1469 | # 3.LoadBalancer,云提供商的负载均衡器向外部暴露服务 1470 | # 4.ExternalName,将服务映射到 DNS 名称 1471 | type: NodePort 1472 | ports: 1473 | - port: 3050 1474 | targetPort: 3000 1475 | # 默认:30000-32767 1476 | nodePort: 31515 1477 | selector: 1478 | component: web 1479 | ``` 1480 | 1481 | 当我们从外部请求 client 时,请求通过 kube-proxy 发送到 Service,Service 再转发到 Pod 暴露的 3000 端口。 1482 | 1483 | 配置文件创建完成后,我们使用 **kubectl apply** 命令对资源应用配置更改: 1484 | 1485 | ```shell 1486 | kubectl apply -f FILENAME [flags] 1487 | ``` 1488 | 1489 | 运行命令,**-f** 选项传入对应配置文件: 1490 | 1491 | ```shell 1492 | wuxianmimi kubernetes-simplek8s % kubectl apply -f ./client-pod.yaml 1493 | pod/client-pod created 1494 | wuxianmimi kubernetes-simplek8s % kubectl apply -f ./client-node-port.yaml 1495 | service/client-node-port created 1496 | wuxianmimi kubernetes-simplek8s % 1497 | ``` 1498 | 1499 | 随后使用 **kubectl get pods** 查看正在运行的 Pods 列表: 1500 | 1501 | ```shell 1502 | kubectl get (-f FILENAME | TYPE [NAME | /NAME | -l label]) [--watch] [--sort-by=FIELD] [[-o | --output]=OUTPUT_FORMAT] [flags] 1503 | ``` 1504 | 1505 | 获取 Pod 列表: 1506 | 1507 | ```shell 1508 | wuxianmimi kubernetes-simplek8s % kubectl get pods 1509 | NAME READY STATUS RESTARTS AGE 1510 | client-pod 1/1 Running 0 2m37s 1511 | wuxianmimi kubernetes-simplek8s % 1512 | ``` 1513 | 1514 | 使用 **kubectl get services** 查看正在运行的服务: 1515 | 1516 | ```shell 1517 | wuxianmimi kubernetes-simplek8s % kubectl get services 1518 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 1519 | client-node-port NodePort 10.102.151.149 3050:31515/TCP 3m6s 1520 | kubernetes ClusterIP 10.96.0.1 443/TCP 150m 1521 | wuxianmimi kubernetes-simplek8s % 1522 | ``` 1523 | 1524 | 接下来就是准备访问服务了,需要访问 minikube 中的服务,只能通过 ip 来访问,使用 **minikube ip** 获取 ip 地址: 1525 | 1526 | ```shell 1527 | wuxianmimi kubernetes-simplek8s % minikube ip 1528 | 192.168.49.2 1529 | wuxianmimi kubernetes-simplek8s % 1530 | ``` 1531 | 1532 | 访问 http://192.168.49.2:31515/ 可以看到 multi-client 这个页面。 1533 | 1534 | ⚠️:这里就会碰到咪咪在文章开头说到的问题,如果你使用 Mac 的话,由于 Docker Desktop 的实现问题,无法使用 IP 地址访问容器。可以在命令行输入 minikube service client-node-port --url 来获取一个本机地址,通过该地址来访问页面。 1535 | 1536 | ![img]() 1537 | 1538 | Kubernetes 启动应用成功! 1539 | 1540 | # 第十三章:了解 Kubernetes 配置 1541 | 1542 | - **更新镜像** 1543 | 1544 | 在 client-pod.yaml 中,我们更改 image 为 **stephengrider/multi-worker**: 1545 | 1546 | ```yaml 1547 | # Kubernetes API 的版本 1548 | apiVersion: v1 1549 | # 这个配置文件生成的对象类型 1550 | kind: Pod 1551 | # 唯一标识对象的一些数据 1552 | metadata: 1553 | name: client-pod 1554 | labels: 1555 | component: web 1556 | # 对象的状态 1557 | spec: 1558 | containers: 1559 | # 容器名,任意取 1560 | - name: client 1561 | # 使用的镜像,镜像必须发布在 Docker Hub 上才能用 1562 | image: stephengrider/multi-worker 1563 | # 暴露的端口 1564 | ports: 1565 | - containerPort: 3000 1566 | ``` 1567 | 1568 | 随后在命令行使用 **kubectl apply** 命令更新配置: 1569 | 1570 | ```yaml 1571 | wuxianmimi kubernetes-simplek8s % kubectl apply -f ./client-pod.yaml 1572 | pod/client-pod configured 1573 | ``` 1574 | 1575 | 过一会儿使用 **kubectl get pods** 查看 pod 状态,发现已经重启了: 1576 | 1577 | ```yaml 1578 | wuxianmimi kubernetes-simplek8s % kubectl get pods 1579 | NAME READY STATUS RESTARTS AGE 1580 | client-pod 1/1 Running 1 (58s ago) 19h 1581 | ``` 1582 | 1583 | 如果需要详细的资源情况,可以使用 **kubectl describe** 命令来获取: 1584 | 1585 | ```shell 1586 | kubectl describe (-f FILENAME | TYPE [NAME_PREFIX | /NAME | -l label]) [flags] 1587 | ``` 1588 | 1589 | 获取 client-pod 详情: 1590 | 1591 | ```shell 1592 | wuxianmimi kubernetes-simplek8s % kubectl describe pod client-pod 1593 | Name: client-pod 1594 | Namespace: default 1595 | Priority: 0 1596 | Service Account: default 1597 | Node: minikube/192.168.49.2 1598 | Start Time: Thu, 12 Jan 2023 16:33:36 +0800 1599 | Labels: component=web 1600 | Annotations: 1601 | Status: Running 1602 | IP: 172.17.0.3 1603 | IPs: 1604 | IP: 172.17.0.3 1605 | Containers: 1606 | client: 1607 | Container ID: docker://e99f30ff3b6fc654849e44e79c2ee74c5cc084271b1d4f66c9443e2a715bce0e 1608 | Image: stephengrider/multi-worker 1609 | Image ID: docker-pullable://stephengrider/multi-worker@sha256:5fbab5f86e6a4d499926349a5f0ec032c42e7f7450acc98b053791df26dc4d2b 1610 | Port: 3000/TCP 1611 | Host Port: 0/TCP 1612 | State: Running 1613 | Started: Fri, 13 Jan 2023 11:33:44 +0800 1614 | Last State: Terminated 1615 | Reason: Completed 1616 | Exit Code: 0 1617 | Started: Thu, 12 Jan 2023 16:34:00 +0800 1618 | Finished: Fri, 13 Jan 2023 11:33:00 +0800 1619 | Ready: True 1620 | Restart Count: 1 1621 | Environment: 1622 | Mounts: 1623 | /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-7797r (ro) 1624 | Conditions: 1625 | Type Status 1626 | Initialized True 1627 | Ready True 1628 | ContainersReady True 1629 | PodScheduled True 1630 | Volumes: 1631 | kube-api-access-7797r: 1632 | Type: Projected (a volume that contains injected data from multiple sources) 1633 | TokenExpirationSeconds: 3607 1634 | ConfigMapName: kube-root-ca.crt 1635 | ConfigMapOptional: 1636 | DownwardAPI: true 1637 | QoS Class: BestEffort 1638 | Node-Selectors: 1639 | Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s 1640 | node.kubernetes.io/unreachable:NoExecute op=Exists for 300s 1641 | Events: 1642 | Type Reason Age From Message 1643 | ---- ------ ---- ---- ------- 1644 | Normal Killing 4m8s kubelet Container client definition changed, will be restarted 1645 | Normal Pulling 4m8s kubelet Pulling image "stephengrider/multi-worker" 1646 | Normal Created 3m24s (x2 over 19h) kubelet Created container client 1647 | Normal Started 3m24s (x2 over 19h) kubelet Started container client 1648 | Normal Pulled 3m24s kubelet Successfully pulled image "stephengrider/multi-worker" in 43.695453104s 1649 | wuxianmimi kubernetes-simplek8s % 1650 | ``` 1651 | 1652 | 输出的信息尾部可以看到有拉取"**stephengrider/multi-worker**"镜像的记录。 1653 | 1654 | - **Deployment** 1655 | 1656 | 刚刚我们更新 image 成功了,但是如果使用这种方法更新端口,你会发现无法更新,在 **kubectl apply** 时会报错。 1657 | 1658 | 当对象配置 kind 为 Pod 时,我们只能更新部分字段,其中不包括端口,而如果要更全面的声明式更新能力,我们需要使用 Deployment。 1659 | 1660 | 在文件夹内创建 client-deployment.yaml: 1661 | 1662 | ```yaml 1663 | apiVersion: apps/v1 1664 | kind: Deployment 1665 | metadata: 1666 | name: client-deployment 1667 | spec: 1668 | # Pod 数量 1669 | replicas: 1 1670 | # Pod 选择器 1671 | selector: 1672 | matchLabels: 1673 | component: web 1674 | template: 1675 | # Pod 1676 | metadata: 1677 | labels: 1678 | component: web 1679 | spec: 1680 | containers: 1681 | - name: client 1682 | image: stephengrider/multi-client 1683 | ports: 1684 | - containerPort: 3000 1685 | ``` 1686 | 1687 | 完成配之后,我们先删除之前创建的 Pod,使用命令 **kubectl delete** 来完成删除工作: 1688 | 1689 | ```shell 1690 | kubectl delete (-f FILENAME | TYPE [NAME | /NAME | -l label | --all]) [flags] 1691 | ``` 1692 | 1693 | 删除 client-pod.yaml 的相关配置: 1694 | 1695 | ```shell 1696 | wuxianmimi kubernetes-simplek8s % kubectl delete -f ./client-pod.yaml 1697 | pod "client-pod" deleted 1698 | wuxianmimi kubernetes-simplek8s % kubectl get pods 1699 | No resources found in default namespace. 1700 | wuxianmimi kubernetes-simplek8s % 1701 | ``` 1702 | 1703 | 删除后,我们应用新的配置: 1704 | 1705 | ```shell 1706 | wuxianmimi kubernetes-simplek8s % kubectl apply -f ./client-deployment.yaml 1707 | deployment.apps/client-deployment created 1708 | wuxianmimi kubernetes-simplek8s % kubectl get deployments 1709 | NAME READY UP-TO-DATE AVAILABLE AGE 1710 | client-deployment 1/1 1 1 21s 1711 | wuxianmimi kubernetes-simplek8s % 1712 | ``` 1713 | 1714 | 接下来更改 client-deployment.yaml 中 containerPort 后,再应用配置就不会报错了。 1715 | 1716 | - **更新镜像版本** 1717 | 1718 | 如果要更新镜像版本,可以使用: 1719 | 1720 | **kubectl set image [object 类型]/[object name] [容器 name]=[新镜像版本]** 1721 | 1722 | - **切换 Docker-client 请求指向** 1723 | 1724 | 使用 **eval $(minikube docker-env)** 命令,可以将 docker-cli 指向的 docker-server 变成 minikube 创建的虚拟机中,可以方便我们调试: 1725 | 1726 | ```shell 1727 | wuxianmimi kubernetes-simplek8s % eval $(minikube docker-env) 1728 | wuxianmimi kubernetes-simplek8s % docker ps 1729 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1730 | bff39aba4d5b 6d3ffc2696ac "/coredns -conf /etc…" 8 minutes ago Up 8 minutes k8s_coredns_coredns-78fcd69978-x84sp_kube-system_b16c591f-cd49-4967-9489-f4654834f8aa_0 1731 | 5f2dc9090d50 d9fa9053808e "/usr/local/bin/kube…" 8 minutes ago Up 8 minutes k8s_kube-proxy_kube-proxy-9bdkr_kube-system_c8cae802-b82a-455b-a44f-791f1541bb0b_0 1732 | f591feca3db1 k8s.gcr.io/pause:3.5 "/pause" 8 minutes ago Up 8 minutes k8s_POD_coredns-78fcd69978-x84sp_kube-system_b16c591f-cd49-4967-9489-f4654834f8aa_0 1733 | 3d4d77d0ecbc k8s.gcr.io/pause:3.5 "/pause" 8 minutes ago Up 8 minutes k8s_POD_kube-proxy-9bdkr_kube-system_c8cae802-b82a-455b-a44f-791f1541bb0b_0 1734 | 428446d5c4cc ba04bb24b957 "/storage-provisioner" 8 minutes ago Up 8 minutes k8s_storage-provisioner_storage-provisioner_kube-system_785bb891-8438-45f8-95ea-2fb7511af55a_0 1735 | 5611755fac61 k8s.gcr.io/pause:3.5 "/pause" 8 minutes ago Up 8 minutes k8s_POD_storage-provisioner_kube-system_785bb891-8438-45f8-95ea-2fb7511af55a_0 1736 | fe491e1de92c 4641e56315a2 "kube-scheduler --au…" 9 minutes ago Up 9 minutes k8s_kube-scheduler_kube-scheduler-minikube_kube-system_6fd078a966e479e33d7689b1955afaa5_0 1737 | 87aac0b31bd5 d5504eacf2d7 "kube-controller-man…" 9 minutes ago Up 9 minutes k8s_kube-controller-manager_kube-controller-manager-minikube_kube-system_f8d2ab48618562b3a50d40a37281e35e_0 1738 | 86681a2dabf7 7605412e3e07 "kube-apiserver --ad…" 9 minutes ago Up 9 minutes k8s_kube-apiserver_kube-apiserver-minikube_kube-system_cf344d9adbf2feb59a7c63da2c6283f8_0 1739 | 1ae60d767e22 2252d5eb703b "etcd --advertise-cl…" 9 minutes ago Up 9 minutes k8s_etcd_etcd-minikube_kube-system_cc51499f38934c109ed290475d9492d0_0 1740 | ed08b7a7550c k8s.gcr.io/pause:3.5 "/pause" 9 minutes ago Up 9 minutes k8s_POD_kube-scheduler-minikube_kube-system_6fd078a966e479e33d7689b1955afaa5_0 1741 | ce7d76aff58a k8s.gcr.io/pause:3.5 "/pause" 9 minutes ago Up 9 minutes k8s_POD_kube-controller-manager-minikube_kube-system_f8d2ab48618562b3a50d40a37281e35e_0 1742 | 9f42fd98a0f2 k8s.gcr.io/pause:3.5 "/pause" 9 minutes ago Up 9 minutes k8s_POD_kube-apiserver-minikube_kube-system_cf344d9adbf2feb59a7c63da2c6283f8_0 1743 | d17349c2242c k8s.gcr.io/pause:3.5 "/pause" 9 minutes ago Up 9 minutes k8s_POD_etcd-minikube_kube-system_cc51499f38934c109ed290475d9492d0_0 1744 | wuxianmimi kubernetes-simplek8s % 1745 | ``` 1746 | 1747 | # 第十四章:多容器应用:Kubernetes 开发环境搭建 1748 | 1749 | 这一章开始使用 Kubernetes 部署更复杂的项目,使用的是学习 docker-compose 时的 docker-complex 那个应用,Kubernetes 版本的架构如下图: 1750 | 1751 | ![img]() 1752 | 1753 | kubernetes-complex 项目架构 1754 | 1755 | 一共需要 11 个配置文件。 1756 | 1757 | 启动模板可以使用咪咪的: 1758 | 1759 | https://github.com/lyf61/Docker-and-Kubernetes-The-Complete-Guide-Complex-App-Kubernetes-Starter 1760 | 1761 | 不过由于我们不会部署在生产环境,且我们使用的 Docker 镜像都是讲师已经发布的,所以开发环境下完全不使用模板也可以。 1762 | 1763 | 首先创建 client 的 service 与 deployment,在项目根目录下创建 k8s 文件夹,随后创建两个文件: 1764 | 1765 | client-deployment.yaml 1766 | 1767 | ```yaml 1768 | apiVersion: apps/v1 1769 | kind: Deployment 1770 | metadata: 1771 | name: client-deployment 1772 | spec: 1773 | replicas: 3 1774 | selector: 1775 | matchLabels: 1776 | component: web 1777 | template: 1778 | metadata: 1779 | labels: 1780 | component: web 1781 | spec: 1782 | containers: 1783 | - name: client 1784 | image: stephengrider/multi-client 1785 | ports: 1786 | - containerPort: 3000 1787 | ``` 1788 | 1789 | client-cluster-ip-service.yaml 1790 | 1791 | ```yaml 1792 | apiVersion: v1 1793 | kind: Service 1794 | metadata: 1795 | name: client-cluster-ip-service 1796 | spec: 1797 | type: ClusterIP 1798 | selector: 1799 | component: web 1800 | ports: 1801 | - port: 3000 1802 | targetPort: 3000 1803 | ``` 1804 | 1805 | 随后使用 **kubectl apply** 应用配置测试一下,这次 **-f** 选项传入文件夹,kubectl 会自动应用文件夹下所有配置: 1806 | 1807 | ```shell 1808 | wuxianmimi kubernetes-complex % kubectl apply -f k8s 1809 | service/client-cluster-ip-service created 1810 | deployment.apps/client-deployment created 1811 | wuxianmimi kubernetes-complex % kubectl get deployment 1812 | NAME READY UP-TO-DATE AVAILABLE AGE 1813 | client-deployment 3/3 3 3 66s 1814 | wuxianmimi kubernetes-complex % kubectl get pod 1815 | NAME READY STATUS RESTARTS AGE 1816 | client-deployment-7cb6c958f7-4c4pc 1/1 Running 0 72s 1817 | client-deployment-7cb6c958f7-4kp2x 1/1 Running 0 72s 1818 | client-deployment-7cb6c958f7-gv78d 1/1 Running 0 72s 1819 | wuxianmimi kubernetes-complex % kubectl get svc 1820 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 1821 | client-cluster-ip-service ClusterIP 10.109.207.213 3000/TCP 80s 1822 | kubernetes ClusterIP 10.96.0.1 443/TCP 18h 1823 | wuxianmimi kubernetes-complex % 1824 | ``` 1825 | 1826 | 可以看到启动成功了。 1827 | 1828 | 继续创建 server 与 worker 相关对象配置: 1829 | 1830 | server-deployment.yaml 1831 | 1832 | ```yaml 1833 | apiVersion: apps/v1 1834 | kind: Deployment 1835 | metadata: 1836 | name: server-deployment 1837 | spec: 1838 | replicas: 3 1839 | selector: 1840 | matchLabels: 1841 | component: server 1842 | template: 1843 | metadata: 1844 | labels: 1845 | component: server 1846 | spec: 1847 | containers: 1848 | - name: server 1849 | image: stephengrider/multi-server 1850 | ports: 1851 | - containerPort: 5000 1852 | ``` 1853 | 1854 | server-cluster-ip-service.yaml 1855 | 1856 | ```yaml 1857 | apiVersion: v1 1858 | kind: Service 1859 | metadata: 1860 | name: server-cluster-ip-service 1861 | spec: 1862 | type: ClusterIP 1863 | selector: 1864 | component: server 1865 | ports: 1866 | - port: 5000 1867 | targetPort: 5000 1868 | ``` 1869 | 1870 | worker-deployment.yaml 1871 | 1872 | ```yaml 1873 | apiVersion: apps/v1 1874 | kind: Deployment 1875 | metadata: 1876 | name: worker-deployment 1877 | spec: 1878 | replicas: 3 1879 | selector: 1880 | matchLabels: 1881 | component: worker 1882 | template: 1883 | metadata: 1884 | labels: 1885 | component: worker 1886 | spec: 1887 | containers: 1888 | - name: worker 1889 | image: stephengrider/multi-worker 1890 | ``` 1891 | 1892 | 测试后,server Pod 中的日志应该显示数据库连接不上,说明其他配置没问题: 1893 | 1894 | ```shell 1895 | wuxianmimi kubernetes-complex % kubectl get pods 1896 | NAME READY STATUS RESTARTS AGE 1897 | client-deployment-7cb6c958f7-4c4pc 1/1 Running 0 32m 1898 | client-deployment-7cb6c958f7-4kp2x 1/1 Running 0 32m 1899 | client-deployment-7cb6c958f7-gv78d 1/1 Running 0 32m 1900 | server-deployment-9bff8dfb-cc7ls 1/1 Running 0 3m13s 1901 | server-deployment-9bff8dfb-dktx4 1/1 Running 0 3m13s 1902 | server-deployment-9bff8dfb-m9mg8 1/1 Running 0 3m13s 1903 | worker-deployment-666c96ffc5-n6gh8 1/1 Running 0 3m13s 1904 | wuxianmimi kubernetes-complex % kubectl logs server-deployment-9bff8dfb-m9mg8 1905 | 1906 | > @ start /app 1907 | > node index.js 1908 | 1909 | Listening 1910 | { Error: connect ECONNREFUSED 127.0.0.1:5432 1911 | at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1161:14) 1912 | errno: 'ECONNREFUSED', 1913 | code: 'ECONNREFUSED', 1914 | syscall: 'connect', 1915 | address: '127.0.0.1', 1916 | port: 5432 } 1917 | ``` 1918 | 1919 | 接下来配置 Redis 与 Postgres: 1920 | 1921 | redis-deployment.yaml 1922 | 1923 | ```yaml 1924 | apiVersion: apps/v1 1925 | kind: Deployment 1926 | metadata: 1927 | name: redis-deployment 1928 | spec: 1929 | replicas: 1 1930 | selector: 1931 | matchLabels: 1932 | component: redis 1933 | template: 1934 | metadata: 1935 | labels: 1936 | component: redis 1937 | spec: 1938 | containers: 1939 | - name: redis 1940 | image: redis:4-alpine 1941 | ports: 1942 | - containerPort: 6379 1943 | ``` 1944 | 1945 | redis-cluster-ip-service.yaml 1946 | 1947 | ```yaml 1948 | apiVersion: v1 1949 | kind: Service 1950 | metadata: 1951 | name: redis-cluster-ip-service 1952 | spec: 1953 | type: ClusterIP 1954 | selector: 1955 | component: redis 1956 | ports: 1957 | - port: 6379 1958 | targetPort: 6379 1959 | ``` 1960 | 1961 | postgres-deployment.yaml 1962 | 1963 | ```yaml 1964 | apiVersion: apps/v1 1965 | kind: Deployment 1966 | metadata: 1967 | name: postgres-deployment 1968 | spec: 1969 | replicas: 1 1970 | selector: 1971 | matchLabels: 1972 | component: postgres 1973 | template: 1974 | metadata: 1975 | labels: 1976 | component: postgres 1977 | spec: 1978 | containers: 1979 | - name: postgres 1980 | image: postgres:10.5 1981 | ports: 1982 | - containerPort: 5432 1983 | ``` 1984 | 1985 | postgres-cluster-ip-service.yaml 1986 | 1987 | ```yaml 1988 | apiVersion: v1 1989 | kind: Service 1990 | metadata: 1991 | name: postgres-cluster-ip-service 1992 | spec: 1993 | type: ClusterIP 1994 | selector: 1995 | component: postgres 1996 | ports: 1997 | - port: 5432 1998 | targetPort: 5432 1999 | ``` 2000 | 2001 | - **Kubernetes 中的 Volume** 2002 | 2003 | 在本项目架构图中,我们发现一个叫 **Postgres PVC** 的东东。如果没有这个对象,当我们 postgres 容器被销毁的时候,postgres 中的数据也同时被销毁了。所以这个对象有点类似我们之前学习 Docker 时 Volume 的概念,但是 Docker 中 Volume 是一种让容器访问外部文件系统的机制,而 Kubernetes 中的 Volume 是一个对象,在 Pod 层面存储数据。 2004 | 2005 | ![img]() 2006 | 2007 | Postgres PVC 2008 | 2009 | - **PersistentVolume** 2010 | 2011 | Kubernetes 中的 Volume 在 Pod 层面存储数据,所以 Pod 的生命周期也会影响数据,这不太好。而 PersistentVolume 解决了这个问题,把数据从 Pod 中“剥离”了出来。 2012 | 2013 | - **PersistentVolumeClaim** 2014 | 2015 | 乍一眼看上去与 PersistentVolume 很像,这两个有什么区别?在[课程的 P186 视频](https://www.bilibili.com/video/BV1hS4y1m7Ma/?p=186)讲师用火柴人动画讲解了很清楚,可以看一下。简单的来说就是 PersistentVolume 是预先静态分配好的一个存储资源,而 PersistentVolumeClaim 是对存储空间的请求和申领,等需要时才会提供。 2016 | 2017 | 创建我们的 PersistentVolumeClaim,在 k8s 目录创建文件 database-persistent-volume-cliam.yaml: 2018 | 2019 | ```yaml 2020 | apiVersion: v1 2021 | # 对于我们来说新的对象类型 2022 | kind: PersistentVolumeClaim 2023 | metadata: 2024 | name: database-persistent-volume-claim 2025 | spec: 2026 | # 访问模式,一共有四种取值: 2027 | # ReadWriteOnce 卷可以被 一个节点 以读写方式挂载 2028 | # ReadOnlyMany 卷可以被 多个节点 只读方式挂载 2029 | # ReadWriteMany 卷可以被 多个节点 以读写方式挂载 2030 | # ReadWriteOncePod 卷可以被 单个Pod 以读写方式挂载 2031 | accessModes: 2032 | - ReadWriteOnce 2033 | resources: 2034 | requests: 2035 | # 要求2GB存储空间 2036 | storage: 2Gi 2037 | ``` 2038 | 2039 | - **StorageClass** 2040 | 2041 | 存储 "类" 的方法,简单来说就是申请一块存储空间时的策略和方法的提供者。 2042 | 2043 | 在本地环境中只有一种方式,在云服务上有各个云服务器厂商提供的“类”,可以通过命令行 **kubectl get storageclass** 来查看当前支持哪些 StorageClass: 2044 | 2045 | ```shell 2046 | wuxianmimi kubernetes-complex % kubectl get storageclass 2047 | NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE 2048 | standard (default) k8s.io/minikube-hostpath Delete Immediate false 20h 2049 | wuxianmimi kubernetes-complex % 2050 | ``` 2051 | 2052 | 随后在 postgres-deployment.yaml 中增加 Volume 的相关配置: 2053 | 2054 | ```yaml 2055 | apiVersion: apps/v1 2056 | kind: Deployment 2057 | metadata: 2058 | name: postgres-deployment 2059 | spec: 2060 | replicas: 1 2061 | selector: 2062 | matchLabels: 2063 | component: postgres 2064 | template: 2065 | metadata: 2066 | labels: 2067 | component: postgres 2068 | spec: 2069 | # 可以由属于 Pod 的容器挂载的卷列表 2070 | volumes: 2071 | - name: postgres-storage 2072 | persistentVolumeClaim: 2073 | claimName: database-persistent-volume-claim 2074 | containers: 2075 | - name: postgres 2076 | image: postgres:10.5 2077 | ports: 2078 | - containerPort: 5432 2079 | # 要挂载到容器文件系统中的 Pod 卷 2080 | volumeMounts: 2081 | - name: postgres-storage 2082 | # 在容器内 Volume 的挂载路径 2083 | mountPath: /var/lib/postgresql/data 2084 | # Volume 中的路径,默认为 ""(卷的根)。 2085 | subPath: postgres 2086 | ``` 2087 | 2088 | 应用配置后,我们在命令行查看 pv 与 pvc 的情况: 2089 | 2090 | ```shell 2091 | wuxianmimi kubernetes-complex % kubectl get pv 2092 | NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE 2093 | pvc-a62cbd16-f788-4daf-b6e2-e3ec77ebdc32 2Gi RWO Delete Bound default/database-persistent-volume-claim standard 28s 2094 | wuxianmimi kubernetes-complex % kubectl get pvc 2095 | NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE 2096 | database-persistent-volume-claim Bound pvc-a62cbd16-f788-4daf-b6e2-e3ec77ebdc32 2Gi RWO standard 30s 2097 | wuxianmimi kubernetes-complex % 2098 | ``` 2099 | 2100 | - **环境变量** 2101 | 2102 | 到目前为止我们还没有设置我们的环境变量,那如何设置数据库的连接信息呢? 2103 | 2104 | 在 worker-deployment.yaml 中增加 Redis 的环境变量: 2105 | 2106 | ```yaml 2107 | apiVersion: apps/v1 2108 | kind: Deployment 2109 | metadata: 2110 | name: worker-deployment 2111 | spec: 2112 | replicas: 1 2113 | selector: 2114 | matchLabels: 2115 | component: worker 2116 | template: 2117 | metadata: 2118 | labels: 2119 | component: worker 2120 | spec: 2121 | containers: 2122 | - name: worker 2123 | image: stephengrider/multi-worker 2124 | # 设置环境变量 2125 | env: 2126 | - name: REDIS_HOST 2127 | value: redis-cluster-ip-service 2128 | - name: REDIS_PORT 2129 | value: "6379" 2130 | ``` 2131 | 2132 | 在 server-deployment.yaml 中,增加 Redis 和 Postgres 的环境变量: 2133 | 2134 | ```yaml 2135 | apiVersion: apps/v1 2136 | kind: Deployment 2137 | metadata: 2138 | name: server-deployment 2139 | spec: 2140 | replicas: 3 2141 | selector: 2142 | matchLabels: 2143 | component: server 2144 | template: 2145 | metadata: 2146 | labels: 2147 | component: server 2148 | spec: 2149 | containers: 2150 | - name: server 2151 | image: stephengrider/multi-server 2152 | ports: 2153 | - containerPort: 5000 2154 | # 设置环境变量 2155 | env: 2156 | - name: REDIS_HOST 2157 | value: redis-cluster-ip-service 2158 | - name: REDIS_PORT 2159 | value: "6379" 2160 | - name: PGUSER 2161 | value: postgres 2162 | - name: PGHOST 2163 | value: postgres-cluster-ip-service 2164 | - name: PGPORT 2165 | value: "5432" 2166 | - name: PGDATABASE 2167 | value: postgres 2168 | ``` 2169 | 2170 | - **Secret** 2171 | 2172 | 还剩最后的 Postgres 密码没有配置,这种敏感信息用明文写在配置文件中不太合适,Kubernetes 为我们提供了一种方式来存储这些敏感信息,这个对象叫 Secret。 2173 | 2174 | 使用 **kubectl create secret generic pgpassword --from-literal PGPASSWORD=12345asdf** 命令创建 Secret。 2175 | 2176 | 获取目前 sercet: 2177 | 2178 | ```shell 2179 | wuxianmimi kubernetes-complex % kubectl get secrets 2180 | NAME TYPE DATA AGE 2181 | default-token-mdzch kubernetes.io/service-account-token 3 26h 2182 | pgpassword Opaque 1 22s 2183 | wuxianmimi kubernetes-complex % 2184 | ``` 2185 | 2186 | sercet 是存在环境变量中的,不同的机器上部署要重新设置 secret。 2187 | 2188 | 随后在 server 与 postgres 中添加密码(**secretKeyRef**): 2189 | 2190 | server-deployment.yaml 2191 | 2192 | ```yaml 2193 | apiVersion: apps/v1 2194 | kind: Deployment 2195 | metadata: 2196 | name: server-deployment 2197 | spec: 2198 | replicas: 3 2199 | selector: 2200 | matchLabels: 2201 | component: server 2202 | template: 2203 | metadata: 2204 | labels: 2205 | component: server 2206 | spec: 2207 | containers: 2208 | - name: server 2209 | image: stephengrider/multi-server 2210 | ports: 2211 | - containerPort: 5000 2212 | # 设置环境变量 2213 | env: 2214 | - name: REDIS_HOST 2215 | value: redis-cluster-ip-service 2216 | - name: REDIS_PORT 2217 | value: "6379" 2218 | - name: PGUSER 2219 | value: postgres 2220 | - name: PGHOST 2221 | value: postgres-cluster-ip-service 2222 | - name: PGPORT 2223 | value: "5432" 2224 | - name: PGDATABASE 2225 | value: postgres 2226 | - name: PGPASSWORD 2227 | # 值来自我们刚刚设置的 Secret 2228 | valueFrom: 2229 | secretKeyRef: 2230 | name: pgpassword 2231 | key: PGPASSWORD 2232 | ``` 2233 | 2234 | postgres-deployment.yaml 2235 | 2236 | ```yaml 2237 | apiVersion: apps/v1 2238 | kind: Deployment 2239 | metadata: 2240 | name: postgres-deployment 2241 | spec: 2242 | replicas: 1 2243 | selector: 2244 | matchLabels: 2245 | component: postgres 2246 | template: 2247 | metadata: 2248 | labels: 2249 | component: postgres 2250 | spec: 2251 | # 可以由属于 Pod 的容器挂载的卷列表 2252 | volumes: 2253 | - name: postgres-storage 2254 | persistentVolumeClaim: 2255 | claimName: database-persistent-volume-claim 2256 | containers: 2257 | - name: postgres 2258 | image: postgres:10.5 2259 | ports: 2260 | - containerPort: 5432 2261 | # 要挂载到容器文件系统中的 Pod 卷 2262 | volumeMounts: 2263 | - name: postgres-storage 2264 | # 在容器内 Volume 的挂载路径 2265 | mountPath: /var/lib/postgresql/data 2266 | # Volume 中的路径,默认为 ""(卷的根)。 2267 | subPath: postgres 2268 | env: 2269 | - name: PGPASSWORD 2270 | # 值来自我们刚刚设置的 Secret 2271 | valueFrom: 2272 | secretKeyRef: 2273 | name: pgpassword 2274 | key: PGPASSWORD 2275 | ``` 2276 | 2277 | # 第十五章:了解 Ingress 与 Ingress Nginx 配置 2278 | 2279 | 在 Kubernetes 中,Ingress 暴露出 HTTP 和 HTTPS 路由,可以接受外部流量后转发内部服务,相当于一个入口。 2280 | 2281 | 课程中使用的 Ingress 实现是 Nginx Ingress,使用的是 ingress-nginx 这个由社区维护的项目,而不是 kubernetes-ingress 这个由 Nginx 公司维护的。 2282 | 2283 | ingress-nginx 官网地址:https://kubernetes.github.io/ingress-nginx/ 2284 | 2285 | - **安装** 2286 | 2287 | 先应用配置: 2288 | 2289 | ```shell 2290 | wuxianmimi kubernetes-complex % kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml 2291 | namespace/ingress-nginx created 2292 | serviceaccount/ingress-nginx created 2293 | serviceaccount/ingress-nginx-admission created 2294 | role.rbac.authorization.k8s.io/ingress-nginx created 2295 | role.rbac.authorization.k8s.io/ingress-nginx-admission created 2296 | clusterrole.rbac.authorization.k8s.io/ingress-nginx created 2297 | clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created 2298 | rolebinding.rbac.authorization.k8s.io/ingress-nginx created 2299 | rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created 2300 | clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created 2301 | clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created 2302 | configmap/ingress-nginx-controller created 2303 | service/ingress-nginx-controller created 2304 | service/ingress-nginx-controller-admission created 2305 | deployment.apps/ingress-nginx-controller created 2306 | job.batch/ingress-nginx-admission-create created 2307 | job.batch/ingress-nginx-admission-patch created 2308 | ingressclass.networking.k8s.io/nginx unchanged 2309 | validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created 2310 | wuxianmimi kubernetes-complex % 2311 | ``` 2312 | 2313 | 在视频中,接下来会使用 **minikube addons enable ingress** 命令,但是咪咪本地运行此命令会报错,网上找了很多解决办法试了试都无法解决问题。 2314 | 2315 | 而且经过测试,这条命令与上一条 kubectl apply 命令只要运行了其中一条,那剩下那条命令运行就会报错。 2316 | 2317 | 这里写继续往下走流程。 2318 | 2319 | - **配置** 2320 | 2321 | 在 k8s 目录创建 ingress-service.yaml: 2322 | 2323 | ```yaml 2324 | # 从 Kubernetes 1.19 起改名了 2325 | # Kubernetes 1.22 开始只支持 networking.k8s.io/v1 2326 | # apiVersion: extensions/v1beta1 2327 | apiVersion: networking.k8s.io/v1 2328 | kind: Ingress 2329 | metadata: 2330 | name: ingress-service 2331 | annotations: 2332 | kubernetes.io/ingress.class: nginx 2333 | # /api/ 的请求转发后重写,删除 /api 部分 2334 | # 新的 apiVersion 需要使用这种写法来匹配 path 的正则 2335 | nginx.ingress.kubernetes.io/rewrite-target: /$1 2336 | spec: 2337 | rules: 2338 | - http: 2339 | paths: 2340 | - path: /(.*) 2341 | # pathType 是必填字段 2342 | pathType: Prefix 2343 | # 由于 apiVersion 不同,格式变了 2344 | backend: 2345 | # serviceName: client-cluster-ip-service 2346 | # servicePort: 3000 2347 | service: 2348 | name: client-cluster-ip-service 2349 | port: 2350 | number: 3000 2351 | - path: /api/(.*) 2352 | pathType: Prefix 2353 | backend: 2354 | # serviceName: server-cluster-ip-service 2355 | # servicePort: 5000 2356 | service: 2357 | name: server-cluster-ip-service 2358 | port: 2359 | number: 5000 2360 | ``` 2361 | 2362 | 咪咪本地的 Kubernetes 版本是 v1.25.3,视频中使用的 apiVersion 已无法使用,所以配置格式有点不同。 2363 | 2364 | 在终端 **kubectl apply** 应用配置,随后通过 **minikube ip** 获取虚拟机 IP 地址,在浏览器访问该地址来访问我们的服务。 2365 | 2366 | Mac 用户还是在终端输入 **minikube service** 命令来获取访问 URL: 2367 | 2368 | ```shell 2369 | wuxianmimi kubernetes-complex % minikube service ingress-nginx-controller --url -n ingress-nginx 2370 | http://127.0.0.1:62664 2371 | http://127.0.0.1:62665 2372 | ❗ Because you are using a Docker driver on darwin, the terminal needs to be open to run it. 2373 | ``` 2374 | 2375 | 访问 http://127.0.0.1:62664: 2376 | 2377 | ![img]() 2378 | 2379 | 启动成功,且可正常使用 2380 | 2381 | 尝试提交了几个值,可以正常运行。 2382 | 2383 | - **minikube 控制台** 2384 | 2385 | 在命令行输入 minikube dashboard 可以打开控制台: 2386 | 2387 | ```shell 2388 | wuxianmimi ~ % minikube dashboard 2389 | 🔌 正在开启 dashboard ... 2390 | ▪ Using image docker.io/kubernetesui/dashboard:v2.7.0 2391 | ▪ Using image docker.io/kubernetesui/metrics-scraper:v1.0.8 2392 | 💡 Some dashboard features require the metrics-server addon. To enable all features please run: 2393 | 2394 | minikube addons enable metrics-server 2395 | 2396 | 2397 | 🤔 正在验证 dashboard 运行情况 ... 2398 | 🚀 Launching proxy ... 2399 | 🤔 正在验证 proxy 运行状况 ... 2400 | 🎉 Opening http://127.0.0.1:59433/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser... 2401 | ``` 2402 | 2403 | 会自动打开页面: 2404 | 2405 | ![img](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/6e13e8686cc03f293c1cc2aebfba4d7ea9add88f.png@942w_561h_progressive.webp)minikube 控制台 2406 | 2407 | # 第十六章:Kubernetes 生产环境部署(Google Cloud) 2408 | 2409 | 使用 Google Cloud 部署。 2410 | 2411 | 略。 2412 | 2413 | # 第十七章:网站域名与证书配置 2414 | 2415 | 略。 2416 | 2417 | # 第十八章:介绍 Skaffold 2418 | 2419 | Skaffold 是一个命令行工具,可以处理构建、推送、部署等工作流。 2420 | 2421 | 官网链接:https://skaffold.dev/ 2422 | 2423 | 视频中演示了在本地开发时提供监听,当代码改动时,自动更新 Kubernetes 中的相关对象。 2424 | 2425 | # 第十九章:结语 2426 | 2427 | 略。 2428 | --------------------------------------------------------------------------------