Docker
docker
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker version
Client:
Version: 18.03.1-ce
API version: 1.37
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:20:16 2018
OS/Arch: linux/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.03.1-ce
API version: 1.37 (minimum version 1.12)
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:23:58 2018
OS/Arch: linux/amd64
Experimental: false
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker info
Containers: 2
Running: 2
Paused: 0
Stopped: 0
Images: 3
Server Version: 18.03.1-ce
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 773c489c9c1b21a6d78b5c538cd395416ec50f88
runc version: 4fc53a81fb7c994640722ac585fa9ca548971871
init version: 949e6fa
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-1062.18.1.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 1.795GiB
Name: iZuf6b2qg1ny365ykqvfc0Z
ID: SKYU:HP4P:3NVM:3P7Z:76PH:SRGM:QKN6:RI2G:SSBD:P5UM:EUNG:45PU
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
安装
一、配置要求
系统:Centos7
Linux 内核:官方建议 3.10 以上
注意:本文的命令使用的是 root 用户登录执行,不是 root 的话所有命令前面要加 sudo
1.查看当前的内核版本
uname -r
[root@hjz ~]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
2.更新 yum 包(使用 root 权限,生产环境中此步操作需慎重)
yum -y update
yum -y update 升级所有包同时也升级软件和系统内核;
yum -y upgrade 只升级所有包,不升级软件和系统内核
3.卸载旧版本(如果之前安装过的话)
yum remove docker docker-common docker-selinux docker-engine
二、安装Docker
1.安装软件包
安装需要的软件包,yum-util 提供yum-config-manager功能,另两个是devicemapper驱动依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
2.设置 yum 源
yum-config-manager --add-repo http://download.docker.com/linux/centos/docker-ce.repo(中央仓库)
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo(阿里仓库)
3.选择docker版本
查看可用版本有哪些
yum list docker-ce --showduplicates | sort -r
4.安装docker
ee 企业版 ce 社区版
选择一个版本并安装:yum install docker-ce-版本号
yum -y install docker-ce-18.03.1.ce
5.启动 Docker
启动docker前准备*
(docker应用需要用到各种端口,逐一设置比较麻烦,建议直接关闭防火墙) 重要的事请说三遍:启动docker前,一定要关闭防火墙、启动docker前,一定要关闭防火墙、启动docker前,一定要关闭防火墙(关闭前可通过查看查看防火墙状态来检验是否关闭)
#关闭
systemctl stop firewalld
#禁止开机启动防火墙
systemctl disable firewalld
systemctl start docker
systemctl enable docker 设置开机自启
#查看是否启动成功有多种方法
systemctl status docker
6.查看docker版本
[root@hjz ~]# docker version
Client:
Version: 18.03.1-ce
API version: 1.37
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:20:16 2018
OS/Arch: linux/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.03.1-ce
API version: 1.37 (minimum version 1.12)
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:23:58 2018
OS/Arch: linux/amd64
Experimental: false
卸载docker
1.卸载依赖
yum remove docker-ce docker-ce-cli containerd.io
2.删除资源
rm -rf /var/lib/docker
三.安装dockerCompose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
https://www.docker.com/
为什么使用容器?
1.上线流程繁琐
开发->测试->申请资源->审批->部署->测试等环节
2.资源的利用效率低
普遍服务器利用率低,造成过多的浪费
3.扩容/缩容不及时
以为高峰期扩容流程繁琐,上线不及时
4.服务器环境臃肿
服务器越来越臃肿,对维护和迁移带来困难
5.环境不一致
构建--运输--运行
底层原理
Docker是这样工作的?
Docker是一个Clinent-Server结构的系统,Docker的守护进程运行在主机上。通过Socket从客户端访问!DockerServer接收到Docker-Client的指令,就会执行这个指令
Docker为什么比VM快?
1.Docker有着比虚拟机更少的抽象层
2.利用的宿主机的内核,vm需要的是Guest OS.
介绍
- 使用最广泛的开源容器引擎
- 一种操作系统级的虚拟化技术
- 依赖于Linux内核特性,Namespace(资源隔离)和Cgroups(资源限制)
- 一个简单的应用程序打包工具
自 2013 年开放源代码发布以来,Docker 成为一项最受欢迎的技术之一。许多公司都在为此做出贡献,并且大量的人正在使用和采用它。但是为什么如此受欢迎?它提供了以前没有的功能?在此博客文章中,我们想更深入地研究 Docker 内部,以了解其工作原理。
这篇文章的第一部分将简要介绍基本的架构概念。在第二部分中,我们将介绍四个主要功能,这些功能构成了 Docker 容器中隔离的基础:1. 用户组,2. 命名空间,3. 可堆叠的图像层和写时复制,4. 虚拟网络桥。在第三部分中,将讨论使用容器和 Docker 时的机遇与挑战。最后,我们回答一些有关 Docker 的常见问题。
设计目标
- 提供简单的应用程序打包工具
- 开发人员和运维人员职责逻辑分离
- 多环境保持一致性
镜像和容器
Image镜像
- Docker image 是一个
read-only
文件 - 这个文件包含文件系统,源码,库文件,依赖,工具等一些运行application所需要的文件
- 可以理解成一个模版
- docker image具有分层的概念
Container容器
- 一个运行中的docker image文件
- 实质是复制image并在image最上层加上一层
read-write
的层(称为container layer
容器层) - 基于同一个image可以创建多个container
容器vs虚拟机
虚拟机:
- 底层计算机硬件--Computer Hardware
- 速度机 ----Host OS
- Hypervisor--虚拟化的核心 模拟多个虚拟机所具备的CPU\网络 ---资源调度
- Guest OS 一个独立的操作系统
- APP运行在对应OS层上的应用程序
docker:
- Docker Engine
性能对比
Container(联合文件系统) | VM | |
---|---|---|
启动速度 | 秒级 | 分钟级 |
运行性能 | 接近原生(操作系统级别的虚拟化)--只是在进程层面-进程隔离(逻辑) | 5%损耗 |
磁盘占用 | MB | GB |
数量 | 成百上千 | 一般几十台 |
隔离性 | 进程级 | 系统级(更彻底) |
封装程度 | 只打包项目代码和依赖关系,共享宿主机内核 | 完整的操作系统 |
容器:
1.容器提供一个基本的独立环境,实现容器隔离、资源限制
2.主要解决应用层面问题,应用快速部署、高效管理
虚拟机:
1.提升服务器资源利用率
2.提供一个完全隔离的环境
基本架构
"Docker 是一个开源项目,可以自动在软件容器内部署应用程序。"- 维基百科
人们在谈论操作系统级虚拟化时通常会引用容器。操作系统级虚拟化是一种方法,其中操作系统的内核允许存在多个隔离的应用程序实例。有许多可用的容器实现,其中之一是 Docker。
Docker 根据镜像定义了在创建容器时应在容器中放置的内容。定义镜像是通过 Dockerfile 文件来实现的。Dockerfile 文件包含有关如何逐步构建映像的说明(不要担心,稍后您将了解有关内部正在进行的操作的更多信息)。例如,以下 Dockerfile 将从包含 OpenJDK 的映像开始,在其中安装 Python 3, 复制镜像中的requirements.txt
文件,然后安装改文件需求的所有 Python 软件包。
FROM openjdk:8u212-jdk-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
Python3=3.5.3-1 \
Python3-pip=9.0.1-2+deb9u1 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt requirements.txt
RUN pip3 install --upgrade -r requirements.txt
映像通常存储在称为 Docker registries 的映像存储库中。Dockerhub 是公共 Docker 注册中心。为了下载映像并启动容器,您需要拥有 Docker 主机。Docker 主机是一台运行 Docker 守护程序的 Linux 机器(守护程序是一个始终在运行,等待工作完成的后台进程)。
为了启动容器,您可以使用 Docker 客户端,该客户端将必要的指令提交给 Docker 守护程序。如果 Docker 守护程序无法在本地找到所请求的映像,它也正在与 Docker 注册表进行对话。下图说明了 Docker 的基本架构 :
需要注意的一点是,Docker 本身并不提供实际的容器化,仅使用 Linux 中可用的容器化。接下来让我们深入了解下其中的技术细节。
镜像(image):
docker镜像就好比一个模版,可以通过这个模版来创建容器服务,xxx镜像----》run---》xxx容器(提供服务器),通过这个镜像可以创建多个容器(最终服务网运行或者项目运行就是在容器中的)
容器(container):
Docker利用容器技术,独立运行一个或者组应用,通过镜像来创建的
启动、停止、删除、基本命令!
目前就可以把这个容器理解为就是一个简易的linux系统
仓库(repository):
仓库就是存放镜像的地方
容器隔离
Docker 通过四个主要概念的组合来实现不同容器的隔离:1)cgroups,2)namespaces,3)stackableimage layers 和 copy on write,4)virtual network bridges。在下面的小节中,我们将详细解释这些概念。
控制组 (Cgroups)
Linux 操作系统管理可用的硬件资源 (内存、CPU、磁盘 I/O、网络 I/O,…),并为进程访问和利用它们提供一种方便的方式。例如,Linux 的 CPU 调度程序会注意每个线程最终都会在 CPU 核心上获得一些时间,这样就不会有应用程序被困在等待 CPU 时间。
控制组 (cgroup) 是一种将资源子集分配给特定进程组的方法。这可以用来,例如,确保即使你的 CPU 忙于 Python 脚本,你的 PostgreSQL 数据库仍然有专用的 CPU 和 RAM。下图在一个 4 个 CPU 核和 16 GB RAM 的示例场景中说明了这一点。
在 Zeppelin 中启动的所有 zeppelin-grp 笔记本将仅使用 Core1 和 Core2,而 PostgreSQL 进程共享 Core3 和 Core4。这同样适用于内存。cGroup 是容器隔离中的一个重要构建块,因为它们允许硬件资源隔离
命名空间
当 cgroups 隔离硬件资源时,命名空间会隔离和虚拟化系统资源。可以虚拟化的系统资源包括进程 ID 、主机名、用户 ID 、网络访问、进程间通信和文件系统。让我们首先深入到一个进程 ID (PID) 命名空间的例子,以使这一点更清楚,然后简要讨论其他命名空间。
PID 命名空间
Linux 操作系统将进程组织在所谓的进程树中。树根是操作系统启动后运行的第一个进程,它的 PID 为 1 。 由于只能存在一个进程树,所有其它的进程 (例如:火狐,终端模拟器, SSH 服务器等等) 需要(直接或间接)由根进程启动。由于根进程初始化了所有其它进程,所以它通常被称为 init 进程。
下图说明了典型进程树的部分功能,其中 init 进程启动了日志服务 ( syslogd )、调度程序 ( cron ) 以及登录 shell ( bash ):
1 /sbin/init
+-- 196 /usr/sbin/syslogd -s
+-- 354 /usr/sbin/cron -s
+-- 391 login
+-- 400 bash
+-- 701 /usr/local/bin/pstree
在进程树内,每个进程都可以看到其他进程,如果他们愿意的话,进程之间可以互发信号 (例如,请求进程停止)。 使用 PID 命名空间虚拟化特定进程及其所有子进程的 PID ,使其认为它有 PID 1 。 之后它将无法看到除自己的子进程之外的其他进程。下图说明了不同的 PID 命名空间是如何隔离两个 Zeppelin 进程的子进程树。
1 /sbin/init
|
- ...
|
+-- 506 /usr/local/zeppelin
1 /usr/local/zeppelin
+-- 2 interpreter.sh
+-- 3 interpreter.sh
+-- 511 /usr/local/zeppelin
1 /usr/local/zeppelin
+-- 2 java
文件系统命名空间
命名空间的另一个用例是 Linux 文件系统。类似于 PID 命名空间,文件系统命名空间虚拟化和隔离树的部分 - 在这种情况下即文件系统树。 Linux 文件系统被组织为一棵树的形式,它有一个树根,通常指 /
。
为了在文件系统级别上实现隔离,命名空间将文件系统树中的节点映射到该命名空间内的虚拟根目录。在这个命名空间中浏览文件系统, Linux 不允许您超出虚拟化的根目录。下面的绘图显示了文件系统的一部分,其中包含了/drives/xx
文件夹中的多个 “虚拟” 文件系统根目录,每个文件夹包含不同的数据。
其它命名空间
除了 PID 和文件系统命名空间之外,还有 其他类型的名称空间 。 Docker 允许您使用它们以实现所需的隔离量。 例如,用户命名空间允许您将容器内的用户映射到外部的不同用户。这可以用于将容器内的根用户映射到外部的非根用户,因此容器内的进程在内部就像管理员一样,在外部它没有特权。
可堆叠图像层和写时复制
现在,我们对硬件和系统资源隔离如何帮助我们构建容器有了更详细的了解,我们将研究 Docker 存储映像的方式。如前所述,Docker 映像就像是容器的蓝图。它带有启动包含它的应用程序所需的所有依赖关系。但是如何存储这些依赖关系?
Docker 将图像持久保存在可堆叠的层中。一层包含对上一层所做的更改。例如,如果您先安装 Python,然后复制 Python 脚本,则映像将具有两个附加层:一层包含 Python 可执行文件,另一层包含脚本。下图显示了全部基于 Ubuntu 的 Zeppelin,Spring 和 PHP 映像。
为了不存储三次 Ubuntu,层是不可变的并且是共享的。 如果发生改变时,Docker 使用写时复制去复制文件。
基于镜像启动 Docker 容器时,Docker 守护进程将为您提供该镜像中包含的所有层,并将其放入该容器的隔离文件系统命名空间中。可堆叠层、写时复制和文件系统命名空间的组合使您能够完全独立于「安装」在 Docker 主机上的内容来运行容器,而不会浪费大量空间。这就是为什么容器比虚拟机更轻量级的原因之一。
虚拟网桥
现在,我们知道了隔离硬件资源 (控制组) 和系统资源 (命名空间) 的方法,以及如何为每个容器提供独立于主机系统 (镜像层) 的预定义依赖集。最后一个构建模块,即「虚拟网桥」,它可以帮助我们隔离容器内的网络堆栈。
网桥是从多个通信网络或网段创建单个聚合网络的计算机网络设备。让我们看看连接两个网段 (LAN 1 和 LAN 2) 的物理网桥的典型设置:
通常我们在 Docker 主机上只有有限的网络接口 (例如物理网卡),所有进程都需要共享对它的访问。为了隔离容器的网络,Docker 允许每个容器创建一个虚拟网络接口。然后,将所有虚拟网络接口连接到主机网络适配器,如下图所示:
本例中的两个容器在其网络命名空间中有自己的 eth0 网络接口。它映射到 Docker 主机上相应的虚拟网络接口 veth0
和 veth1
。虚拟网桥 docker0 将主机网络接口 eth0
连接到所有容器网络接口。
Docker 为您提供了配置网桥的自由,因此,您可以只向外部公开特定端口,或直接将两个容器连接在一起 (例如,数据库容器和需要访问它的应用程序),而不向外部公开任何内容。
连接点
利用前面描述的技术和功能,我们现在可以 “容器化” 我们的应用程序。虽然可以使用 cgroup、命名空间、虚拟网络适配器等手动创建容器,但 Docker 是一个方便且几乎没有开销的工具。它处理所有手动、配置密集型任务,使软件开发人员而不仅仅是 Linux 专家可以访问容器。
实际上有一位 Docker 工程师提供了一篇 “不错的演讲”,他演示了如何手动创建容器,也解释了我们在这一小节中介绍的细节。
Docker 的机遇和挑战
到现在,很多人每天都在使用 Docker。容器增加了什么好处?Docker 提供了以前没有的东西吗?最后,您对应用程序进行容器化所需的一切在 Linux 中已经存在很长时间了,不是吗?
让我们看看在迁移到基于容器的设置时您所拥有的一些机会 (当然不是详尽的列表)。当然,在采用 Docker 时,不仅有机会,也有挑战,可能会给您带来困难。我们还将在本节中列举一些。
Opportunities
Docker 支持 DevOps。DevOps 理念试图将开发和操作活动连接起来,使开发人员能够自行部署其应用程序。您构建它、运行它。通过基于 Docker 的部署,开发人员可以直接将其工件和所需的依赖项一起发布不用担心依赖冲突。它还允许开发人员编写更复杂的测试并更快地执行它们,例如,在另一个容器中创建一个真实的数据库,并在几秒钟内将其链接到笔记本电脑上的应用程序 (请参见 Testcontainers)。
容器提高了部署的可预测性。不再 “运行” 在我的机器上”。不再有失败的应用程序部署,因为一台计算机安装了不同版本的 Java。您只需构建一次映像,就可以在任何地方运行它 (假设安装了 Linux 内核和 Docker)。
采用率高,与许多著名的集群管理器很好地集成。使用 Docker 的一个重要方面是其周围的软件生态系统。如果您计划进行大规模操作,那么您将无法使用一个或另一个集群管理器。如果你决定让其他人来管理你的部署 (如 Google Cloud、Docker Cloud、Heroku、AWS 等等),或者想维护你自己的集群管理器 (如 Kubernetes、Nomad、Mesos),有很多解决方案可供选择。
轻量级容器支持快速故障恢复或自动扩展。想象一下运行一个在线商店。在圣诞节期间,人们将开始攻击您的 web 服务器,而您当前的设置在容量方面可能不够。考虑到您有足够的免费硬件资源,再启动几个承载 web 应用程序的容器只需几秒钟。也可以通过将容器迁移到新机器来恢复发生故障的机器。
挑战
容器给人一种错误的安全感。在保护应用程序安全方面有许多陷阱。认为把它们放在容器里是保护它们的一种方法是错误的。容器本身并不能保护任何东西。如果有人破解了您的容器化 web 应用程序,他可能会被锁定到名称空间中,但根据设置的不同,有几种方法可以避免这种情况。意识到这一点,并尽可能多地投入到安全性上,就像没有 Docker 一样。
Docker 使人们可以轻松地部署半生不熟的解决方案。选择您最喜欢的软件,并将其名称输入谷歌搜索栏,添加 “Docker”。你可能会在 Dockerhub 上找到至少一个甚至几十个已经公开的包含你的软件的图片。所以为什么不直接执行它,给它一个机会呢?什么会出错?很多事情都会出错。当把东西放进容器里时,它看起来会非常闪亮,令人惊叹,人们也不再关注里面的实际软件和配置了。
(fat)容器反模式会导致大型的、难以管理的部署构件。 我看到过 Docker 映像,这些映像要求您在创建容器时为内部不同的应用程序公开 20 多个端口。Docker 的哲学是一个容器应该做一项工作,你应该把它们组合起来,而不是让它们变得更重。如果您最终将所有工具放在一个容器中,您将失去所有优势,内部可能有不同版本的 Java 或 Python,并最终得到 20 GB 无法管理的映像。
调试某些情况可能仍需要深入的 Linux 知识。您可能听到同事说 XXX 不能与 Docker 一起工作。之所以会发生这种情况,有多种原因。如果某些应用程序不能正确区分它们绑定到的网络接口和它们通告的网络接口,则它们在桥接网络命名空间中运行时会出现问题。另一个问题可能与 cgroups 和名称空间有关,其中共享内存方面的默认设置与您最喜欢的 Linux 发行版上的设置不同,导致在容器内运行时出现 OOM 错误。然而,大多数问题实际上并不与 Docker 相关,而是与应用程序设计不当有关,并且它们并不是那么频繁。但它们仍然需要对 Linux 和 Docker 如何工作有一些更深入的了解,这并不是每个 Docker 用户都有的。
常见问题
容器和虚拟机有什么区别?
在不太深入了解虚拟机 (VM) 体系结构的情况下,让我们从概念层面看两者之间的主要区别。容器在操作系统内部运行,使用内核功能来隔离应用程序。另一方面,VM 需要在操作系统内部运行的管理程序。然后,管理程序创建可由另一组操作系统访问的虚拟硬件。下图比较了基于虚拟机的应用程序设置和基于容器的设置。
如您所见,基于容器的设置具有较小的开销,因为它不需要为每个应用程序添加一个额外的操作系统。这是因为容器管理器 (例如 Docker) 直接使用操作系统功能以更轻量级的方式隔离应用程序。
这是否意味着容器优于虚拟机?这要看情况。这两种技术都有各自的用例,有时甚至可以将它们结合起来,在 VM 中运行容器管理器。有很多博客文章在讨论这两种解决方案的利弊,所以我们现在不打算详细讨论。重要的是要了解差异,不要将容器视为某种 “轻量级虚拟机”,因为它们在内部是不同的。
容器包含什么?
看看容器的定义以及我们到目前为止所学到的知识,我们可以有把握地说,可以使用 Docker 来部署隔离的应用程序。通过将控制组和命名空间与可堆叠的图像层和虚拟网络接口以及虚拟网桥相结合,我们拥有了完全隔离应用程序所需的所有工具,还可能将进程锁定在容器中。现实表明,这并不容易。首先,它需要正确配置,其次,您会注意到完全隔离的容器在大多数情况下没有多大意义。
最后,您的应用程序需要以某种方式产生一些副作用(将数据保存到磁盘,通过网络发送数据包,...)。因此,您最终将通过转发网络流量或将主机卷装入您的文件系统命名空间来打破隔离。也不需要使用所有可用的命名空间功能。虽然默认启用网络、PID 和文件系统命名空间功能,但使用用户 ID 命名空间需要您添加额外的配置选项。
所以假设仅仅通过将某些东西放入容器中就可以保证容器的安全是错误的。例如,AWS 使用一个名为 Firecracker 的轻量级虚拟机引擎来安全和多租户执行短期工作负载。
容器是否能使我的生产环境更稳定?
有些人认为容器可以提高稳定性,因为它们可以隔离错误。如果正确配置的命名空间和 Cgroups 将限制一个进程出现恶意的副作用,那么这是正确的,但实际上需要记住一些事情。
如前所述,容器仅在正确配置时才包含,而且大多数时候您希望它们与系统的其他部分交互。因此,可以说容器有助于提高部署的稳定性,但您应该始终记住,它不会保护您的应用程序不受失败的影响。
结论
Docker 是以或多或少可重现和隔离的方式独立部署应用程序的伟大技术。一如既往,没有万能的解决方案,在选择 Docker 作为您选择的工具之前,您应该了解您在安全性、性能、可部署性、可观察性等方面的要求。
幸运的是,Docker 周围已经有一个巨大的工具生态系统。可以根据需要添加服务发现、容器编排、日志转发、加密等用例的解决方案。我想引用我最喜欢的一条推文来结束这篇帖子:
"把已经坏掉的软件放进 Docker 容器并不会减少它的损坏。" - @sadserver
Docker命令
帮助命令
docker version #显示docker的版本信息
docker info #显示docker的系统信息,包括镜像和容器的数量
docker 命令 --help #万能命令
镜像命令
- docker images 查看所有本地的主机上的镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
#解释
REPOSITORY 镜像的创库源
TAG 镜像的标签
IMAGE ID 镜像的创建时间
CREATED 镜像的大小
SIZE 镜像的大小
#可选项
-a, --all #列出所有的镜像
-q, --quiet #只显示镜像的id
- docker search 搜索镜像
[root@hjz ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 13338 [OK]
mariadb MariaDB Server is a high performing open sou… 5095 [OK]
phpmyadmin phpMyAdmin - A web interface for MySQL and M… 654 [OK]
percona Percona Server is a fork of the MySQL relati… 592 [OK]
bitnami/mysql Bitnami MySQL Docker Image 78 [OK]
[root@hjz ~]# docker search --help
Usage: docker search [OPTIONS] TERM
Search the Docker Hub for images
Options:
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print search using a Go template
--limit int Max number of search results (default 25)
--no-trunc Don't truncate output
#可选项,通过搜藏来过滤
--filter = STARS =3000 #搜索出来的镜像就是STARS大于3000的
- docker pull 下载镜像
#下载镜像 docker pull 镜像[:tag]
[root@hjz ~]# docker pull mysql
Using default tag: latest #如果不写tag,默认就是latest
latest: Pulling from library/mysql
5ed150ed0abe: Pull complete #分层下载,docker iamge 的核心 联合文件系统
0fede58e17ac: Pull complete
994a6ddd6efe: Pull complete
028bda79779b: Pull complete
426fbe9e56a2: Pull complete
1a00e58dd193: Pull complete
4a4f64494005: Pull complete
fba8ab3534a7: Pull complete
2695938edf88: Pull complete
3754e2587bed: Pull complete
1b9f154543e7: Pull complete
Digest: sha256:147572c972192417add6f1cf65ea33edfd44086e461a3381601b53e1662f5d15
Status: Downloaded newer image for mysql:latest #真实地址
#等价于它
docker pull mysql
docker pull docker.io/library/mysql:latest
#指定版本下载
[root@hjz ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
0056409b8e89: Pull complete
219bd535343d: Pull complete
f220ee65eb90: Pull complete
7bbb395b2290: Pull complete
645e487e5f0a: Pull complete
a9fa38d2e1fb: Pull complete
e1d9f4f7e8b4: Pull complete
e03fcfe5d90e: Pull complete
74c4d4272e30: Pull complete
e3a8ad6eeebe: Pull complete
919524a8718b: Pull complete
Digest: sha256:94176d0ad4ed85767fc0d74b8071387109a0390e7c1afd39788269c96d2dad74
Status: Downloaded newer image for mysql:5.7
#联合文件系统--当下载的文件的内容是有相对重复时:可以复用
- docker rmi删除镜像
[root@hjz ~]# docker rmi -f 262701d58edd #删除指定的容器
docker rmi -f $(docker images -af)
容器命令
说明:我们有了镜像才可以创建容器
docker pull centos
新建容器并启动
docker run [可选参数] image
#参数说明
--name="Name" 容器名字 tomcat01 tomcar02 用来区分容器
-d 后台方式运行
-it 使用交互方式运行,进行容器查看内容
-p 指定容器的端口 -p 8080:8080
-p ip:主机端口:容器端口
-p 机端口:容器端口 (常用)
容器端口
-p 随机指定端口
#测试,启动进入容器
[root@hjz ~]# docker run -it centos /bin/bash
[root@1ae8c19ba05a /]# bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@1ae8c19ba05a /]# exit
exit
列出所有的运行的容器
#docker ps 命令
#列出当前正在运行的容器
-a #列出当前正在运行的容器+带出历史运行过的容器
-n=?#显示最近创建的容器
-q #只显示ID
[root@hjz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@hjz ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ae8c19ba05a centos "/bin/bash" 5 minutes ago Exited (0) About a minute ago peaceful_mccarthy
退出容器:
exit #退出容器
Ctrl + P +Q#容器不停止退出
删除容器
docker rm 容器id #删除指定容器
docker rm -f $(docker ps -aq) #删除全部容器
docker ps -a -q|xargs docker rm #删除所有的容器
启动和停止容器的操作
docker start 容器id #启动容器
docker restart 容器id #重启容器
docker stop 容器id #停止容器
docker kill 容器id #强制停止当前容器
常用其他命令
后台启动容器
#命令 docker run -d 进行名!
[root@hjz ~]# docker run -d centos
#问题docker ps,发现centos停止了
#常见的坑,docker容器使用后台运行,就必须要有一个前台进程,docker发现没有应用就会自#动停止,nginx 容器启动后,发现自己没有服务就立即停止了,就是没有程序了
查看日志
docker logs
#docker logs -tf --tail 10 容器id
#自己编写一段shell脚本
[root@hjz ~]# docker run -d centos /bin/sh -c "while true;do echo hjz;sleep 1;done"
[root@hjz ~]# docker ps
......
#显示日志
-tf #显示日志
--tail number #要显示的日志条数
查看容器中的进程信息
[root@hjz ~]# docker top edc3b9c7cc44
UID PID PPID C STIME TTY TIME CMD
root 1621 1606 0 11:13 ? 00:00:00 /bin/sh -c while true;do echo hjz;sleep 1;done
root 1715 1621 0 11:14 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
查看镜像的元数据
docker inspect 容器的id
.....
进入当前正在运行的容器
#我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置
#命令
docker exec -it 容器id bashShell
#测试
[root@hjz ~]# docker exec -it edc3b9c7cc44 /bin/bash
[root@edc3b9c7cc44 /]# ls
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@edc3b9c7cc44 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:03 ? 00:00:00 /bin/sh -c while true;do echo hj
root 76 0 0 08:04 pts/0 00:00:00 /bin/bash
root 99 1 0 08:05 ? 00:00:00 /usr/bin/coreutils --coreutils-p
root 100 76 0 08:05 pts/0 00:00:00 ps -ef
#命令
docker attach 容器id
docker exec
进入容器开启一个新的终端,可以在里面操作docker
进入容器正在执行的终端,不会启动新的进程
从容器内拷贝文件到主机上
docker cp 容器id:容器内路径 目的主机路径
例子:
[root@hjz ~]# docker run -it centos /bin/bash
[root@75474c95f0cb /]# [root@hjz ~]#
[root@hjz ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
75474c95f0cb centos "/bin/bash" 24 seconds ago Up 23 seconds wizardly_hoover
[root@hjz ~]# docker attach 75474c95f0cb
[root@75474c95f0cb /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@75474c95f0cb /]# cd home
[root@75474c95f0cb home]# ls
[root@75474c95f0cb home]# mkdir a.txt
[root@75474c95f0cb home]# ls
a.txt
[root@75474c95f0cb home]# exit
exit
[root@hjz ~]# docker cp 75474c95f0cb:/home/a.txt /home
[root@hjz ~]# cd /home
[root@hjz home]# ls
a.txt hjz
#拷贝是一个手动的过程,未来我们使用-v卷的技术,可以实现同步数据
命令分析
容器
操作 | 命令(全) | 命令(简) |
---|---|---|
容器的创建 | docker container run |
docker run |
容器的列出 | docker container ls | docker ps |
容器的列出(up 和exit) | docker container ls -a | docker ps -a |
容器的停止 | docker container stop |
docker stop |
容器的删除 | docker container rm <name or | docker rm |
docker 命令列表
命令 | 作用 |
---|---|
attach | 绑定到运行中容器的 标准输入, 输出,以及错误流(这样似乎也能进入容器内容,但是一定小心,他们操作的就是控制台,控制台的退出命令会生效,比如redis,nginx… |
build | 从一个 Dockerfile 文件构建镜像 |
commit | 把容器的改变 提交创建一个新的镜像 |
cp | 容器和本地文件系统间 复制 文件/文件夹 |
create | 创建新容器,但并不启动(注意与docker run 的区分)需要手动启动。start\stopdiff 检查容器里文件系统结构的更改【A:添加文件或目录 D:文件或者目录删除 C:文件或者目录更改】 |
events | 获取服务器的实时事件 |
exec | 在运行时的容器内运行命令 |
export | 导出容器的文件系统为一个tar文件。commit是直接提交成镜像,export是导出成文件方便传输 |
history | 显示镜像的历史 |
images | 列出所有镜像 |
import | 导入tar的内容创建一个镜像,再导入进来的镜像直接启动不了容器。/docker-entrypoint.sh nginx -g 'daemon of;'docker ps --no-trunc 看下之前的完整启动命令再用他 |
info | 显示系统信息 |
inspect | 获取docker对象的底层信息 |
kill | 杀死一个或者多个容器 |
load | 从 tar 文件加载镜像 |
login | 登录Docker registry |
logout | 退出Docker registry |
logs | 获取容器日志;容器以前在前台控制台能输出的所有内容,都可以看到 |
pause | 暂停一个或者多个容器 |
port | 列出容器的端口映射 |
ps | 列出所有容器 |
pull | 从registry下载一个image 或者repository |
push | 给registry推送一个image或者repository |
rename | 重命名一个容器 |
restart | 重启一个或者多个容器 |
rm | 移除一个或者多个容器 |
rmi | 移除一个或者多个镜像 |
run | 创建并启动容器 |
save | 把一个或者多个镜像保存为tar文件 |
search | 去docker hub寻找镜像 |
start | 启动一个或者多个容器 |
stats | 显示容器资源的实时使用状态 |
stop | 停止一个或者多个容器 |
tag | 给源镜像创建一个新的标签,变成新的镜像 |
top | 显示正在运行容器的进程 |
unpause | pause的反操作 |
update | 更新一个或者多个docker容器配置 |
version | Show the Docker version information |
container | 管理容器 |
image | 管理镜像 |
network | 管理网络 |
volume | 管理卷 |
Docker 镜像
镜像是什么?
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。
对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。
为什么 Docker 镜像要采用这种分层结构呢
最大的一个好处就是 - 共享资源
比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,
同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
build
docker image build -t hello .
#-t 后面是标签 .代表当前目录下有dockerfile文件
#推荐
docker image build -t dockerHub的用户ID/hello:1.0 .
重新命名docker镜像的tag
docker image tag 镜像的名字|ID 重命名的镜像名字
docker image tag hello dockerHub的ID/hello
#执行
docker container run -it xxxx/hello python3 /hello.py
commit命令
docker commit 提交容器成为一个新的副本
#命令和git原理类似
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名字:[tag]
docker数据卷
什么是容器数据卷
docker的理念回顾·:
将应用和环境打包成一个镜像!
数据?如果数据都在容器中,那么我们容器删除,数据就会丢失:数据可以持久化
MySQL,容器删了,删库跑路!MySQL数据可以存储在本地
容器之间可以有一个数据共享的技术!Docker容器中产生的数据,同步到本地!
这就是卷技术!目录的挂载,将我们容器内的目录,挂载到linux上面
总结:容器的持久化和同步操作,容器间数据共享
数据卷命令
docker run -it -v 主机的目录地址:容器目录
具名挂载和匿名挂载
#匿名挂载
docker run -d -p 10020:3306 --name nginx01 -v /etc/nginx nginx
#查看所有卷的情况-
docker volume ls
所有的docker容器内的卷,没有指定目录的情况下都是在:/var/lib/docker/volumes/xxx/_data
拓展
#通过-v 容器内路径 ,ro rw 改变读写权限
docker run -d -p --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -p --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
#ro 只能从外部改变
Dockerfile
- 用于构建docker镜像的文件
- 包含了构建镜像所需要的“指令”
- 有其特定的语法规则
Dckerfile 就是用来构建docker镜像文件,命令脚本~
#创建一个Dockerfile文件
FROM centos
VOLUME ["volume01","volume02"]
CMD echo "---end---"
CMD /bin/bash
docker build -f /home/docker-test-volume/dockerfile -t hjz/centos:1.0 .
COPY和ADD
两者都可以把local的一个文件复制到镜像里面,如果目标目录不存在,则自动创建
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
比如把本地的hello.py复制到/app目录下。 /app目录不存在则自动创建
ADD
如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动的去压缩文件
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/
- 普通文件复制 -
COPY
- 压缩文件 - ·
ADD
WORKDIR
#设置工作路径,类似于cd 如果工作路径不存在则创建对应的目录
ARG vs ENV
ARG
和ENV
是经常容易混淆的两个Dockerfile的语法,都可以用来设置一个变量,但是有不同
ENV
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz&& \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz&& \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo&& \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
ARG
FROM ubuntu:21.04
ARG VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz&& \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz&& \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo&& \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
区别
使用ARG
只会在创建容器的时候使用相关的变量,ENV
在创建容器后可以依然使用 命令查看env
技巧
- 不需要打开修改相应的dockerfile文件就可以改变值
docker image build -f .\Dcokerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .
CMD
CMD可以来设置容器启动时默认会执行的命令
- 容器启动时默认执行的命令
- 如果docker container run启动时指定了其他命令,则CMD命令会被忽略
- 如果定义了多个CMD,只有最后一个会被执行。
dcoker system prune -f 删除所有停止的容器
docker container run --rm -it ipinfo ipinfo 8.8.8.8 在启动容器是就会删除
ENTRYPOINT
可以设置容器启动时要执行的命令
- ENTRYPOINT 所设置的命令一定会被执行
CMD
设置的命令,可以在docker container run 时传入其他命令,覆盖掉CMD
的命令 - ENTRYPOINT 和
CMD
可以联合使用,ENTRYPOINT设置执行的命令,CMD传递参数
ENTRYPOINT
和CMD
可以同时支持shell格式和Exec格式
数据卷容器
#启动3个容器测试,通过我们自己写的镜像启动
docker run -d --name centos01 -v /home/volume01:volume01 centos
#继承
docker run -d --name centos02 --volumes-from centos01 centos
结论:
容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止,但是你持久到
本地则不会删除
步骤
- 编写一个dockerfile 文件
- docker build 构成一个镜像文件
- docker run 运行镜像
- docker push 发布镜像 (DockerHub、阿里云镜像厂库)
FROM #一切从这里开始
MAINTAINER #镜像是谁写的 姓名+邮箱
RUN #镜像构建的时候需要运行的命令
ADD #步骤 添加内容
WORKDIR #镜像的工作目录
VOLUME #挂载的目录位置
EXPOSE #暴露端口
CMD #指定这个容器启动时候要运行的命令,只有最后一个会执行,会被替代
ENTRYPOINT #指定这个容器执行的命令,可以追加
ONBUILD #当构建一个被继承的 Dockerfile这个时候就会运行ONBUILD命令
COPY #类似ADD命令,将我们的文件拷贝到镜像中
ENV #构建的时候设置环境变量
LABEL #注释
FROM centos
MAINTAINER hjz<haojingzhaoemail@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash
#构建镜像
#命令 docker build -f dockerfile文件路径 -t 镜像名:[tag] . 注意一定要有 . 点
#docker history 容器ID 查看容器如何写的dockerfile
CMD和ENTRYPOINT有什么区别
CMD #指定这个容器启动时候要运行的命令,只有最后一个会执行,会被替代
ENTRYPOINT #指定这个容器执行的命令,可以追加
测试:
FROM centos
CMD ["ls","-a"]
如果执行docker run 容器id
会显示正常的数据,但是要是执行docker run 容器id -l
就会错误。原因cmd执行会替换命令
FROM centos
ENTRYPOINT ["ls","-a"]
发布自己的镜像
DockerHub
- 地址`https://hub.docker.com/注册自己的账号
- 确定这个账号可以登录
- 在我们服务器上提交自己的镜像
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker login --help
Usage: docker login [OPTIONS] [SERVER]
Log in to a Docker registry
Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
- 登录完后就可以提交镜像了
docker login -u xxxxx
- 提交镜像
docker images
...
demo laster
...
#先为demo增加一个tag标签
docker tag 镜像id hjz/demo:1.0
#镜像变成
...
hjz/demo 1.0
demo last
...
#再push镜像到github
docker push hjz/demo:1.0
阿里云镜像服务
- 找到阿里云
- 容器镜像服务
- 创建一个命名空间
- 创建容器镜像
- 浏览阿里云
Docker网络
基础知识:
ip地址的查看
- Windows
ipConfig
- Linux
ip addr
- Linux
ifConfig
eth0 ---对外通信 docker0 --docker网络
测试网络的连通性
ping
命令telnet
命令telnet www.baidu.com 80
traceroute
命令traceroute www.baidu.com
路径跟踪curl
命令
清空所有的环境
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo #本机回环地址
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:2a:57:a2 brd ff:ff:ff:ff:ff:ff
inet 172.30.105.237/20 brd 172.30.111.255 scope global dynamic eth0#阿里云内外地址
valid_lft 315115621sec preferred_lft 315115621sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:fd:64:18:2a brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0#docker0地址
valid_lft forever preferred_lft forever
19: veth4a07f4b@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 8e:ae:a4:ae:1e:43 brd ff:ff:ff:ff:ff:ff link-netnsid 1
33: vethca4b8a9@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 36:6e:41:ac:74:20 brd ff:ff:ff:ff:ff:ff link-netnsid 2
docker是如何处理容器网络访问的?
#查看容器内部的网络地址 ip addr
docker exec -it 容器id ip addr
root@localhost ~]# docker exec -it chemex ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
#思考,linux能不能ping 通容器内部
#linux 可以ping 通 docker
#ping 172.17.0.3 是可以ping通的
原理
192.168.0.1 路由器--- 同一个网段是可以ping
192.168.0.3 手机 --- 通的
- 我们每启动一个docker容器,docker都会给docker容器分配一个ip,我们只要安装了docker,就会有一个网卡docker0桥接模式,使用的技术是evth-pair技术!
- 在启动容器测试,发现了又多一对网卡~!
valid_lft forever preferred_lft forever
19: veth4a07f4b@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 8e:ae:a4:ae:1e:43 brd ff:ff:ff:ff:ff:ff link-netnsid 1
33: vethca4b8a9@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 36:6e:41:ac:74:20 brd ff:ff:ff:ff:ff:ff link-netnsid 2
我们发现这些网卡都是一对一对的,envth-pair 就是一对的虚拟设备接口,他们都是成对出现的,一段连接诶着协议,一段彼此连接
正因为有这个特性,env-pair充当一个桥梁,连接各种学你的网络设备
OpenStac Docker 容器之间的连接,OVS的连接,都是使用 env-pair技术
- 测试容器之间是否可以ping通--可以
--link
思考一个场景,我们编写了一个微服务,database url=ip; 项目不重启 ,数据库ip换掉了,我们希望可以处理这个问题,可以名字来进行访问容器?
docker run -d -P --name tomcat03 --link tomcat02 tomcat
docker exec -it tomcat03 ping tomcat02
#反向ping无法ping通 docker exec -it tomcat02 ping tomcat03
原理探究:
其实这个Tomcat03就是在本地配置了tomcat02的配置?
docker exec -it tomcat03 cat /etc/host
127.0.0.1 localhost
...
172.18.0.3 tomcat02 312857784cd4
...
本质探究:--link就是我们在host配置中增加了一个172.18.0.3 tomcat02 312857784cd4
我们现在玩Docker 已经不建议使用--link了!
自定义网络!不适用docker0!
docekr0问题:他不支持容器名连接访问!
自定义网络
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker network --help
Usage: docker network COMMAND
Manage networks
Options:
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Run 'docker network COMMAND --help' for more information on a command.
查看所有的docker网络:
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
f88882d941af bridge bridge local
0e4c1f32b3f8 host host local
1eed6207af60 none null local
网络模式
- bridge:桥接docker(默认)
- none:不配置网络
- host:和宿主机共享网络
- container:容器网络连通!(用的少,局限性很好)
测试:
#我们直接启动的命令 --net bridge而这个就是我们的docker0
docker run -d -P --name tomcat01 tomcat
docker run -d -p --name tomcat01 --net bridge tomcat
#docker0 的特点 :默认。域名不能访问 --link可以打通连接!
#我们可以自定义一个网络
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
#子网最多支持255*255个 默认的网关是192.168.0.1 网络名称是mynet
#查看自己写的网络
docker network inspect mynet
#启动容器
docker run -d -P --name tomcat-net-01 --net mynet tomcat
docker run -d -P --name tomcat-net-02 --net mynet tomcat
网络连通
[root@iZuf6b2qg1ny365ykqvfc0Z ~]# docker connect --help
Usage: docker COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default "/root/.docker")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")
(default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/root/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/root/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/root/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Management Commands:
config Manage Docker configs
container Manage containers
image Manage images
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes
Run 'docker COMMAND --help' for more information on a command.
连通网络
docker network connect mynet tomcat01
#测试打通 tomcat
#连通之后就是将tomcat01放到mynet网络下
#一个容器两个ip地址
#公网ip 私网ip
Docker Compose
安装
curl -L https://get.daocloud.io/docker/compose/releases/download/v2.4.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
#将可执行权限应用于二进制文件:
sudo chmod +x /usr/local/bin/docker-compose
#创建软链:
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
#测试是否安装成功:
docker-compose version
基本语法
version: "3" # 指定docker-compose语法版本
services: # 从以下定义服务配置列表
server_name: # 可将server_name替换为自定义的名字,如mysql/php都可以
container_name: container_name # 指定实例化后的容器名,可将container_name替换为自定义名
image: xxx:latest # 指定使用的镜像名及标签
build: # 如果没有现成的镜像,需要自己构建使用这个选项
context: /xxx/xxx/Dockerfile # 指定构建镜像文件的路径
dockerfile: .... # 指定Dockerfile文件名,上一条指定,这一条就不要了
ports:
- "00:00" # 容器内的映射端口,本地端口:容器内端口
- "00:00" # 可指定多个
volumes:
- test1:/xx/xx # 这里使用managed volume的方法,将容器内的目录映射到物理机,方便管理
- test2:/xx/xx # 前者是volumes目录下的名字,后者是容器内目录
- test3:/xx/xx # 在文件的最后还要使用volumes指定这几个tests
volumes_from: # 指定卷容器
- volume_container_name # 卷容器名
restarts: always # 设置无论遇到什么错,重启容器
depends_on: # 用来解决依赖关系,如这个服务的启动,必须在哪个服务启动之后
- server_name # 这个是名字其他服务在这个文件中的server_name
- server_name1 # 按照先后顺序启动
links: # 与depend_on相对应,上面控制容器启动,这个控制容器连接
- mysql # 值可以是- 服务名,比较复杂,可以在该服务中使用links中mysql代替这个mysql的ip
networks: # 加入指定的网络,与之前的添加网卡名类似
- my_net # bridge类型的网卡名
- myapp_net # 如果没有网卡会被创建,建议使用时先创建号,在指定
environment: # 定义变量,类似dockerfile中的ENV
- TZ=Asia/Shanghai # 这里设置容器的时区为亚洲上海,也就解决了容器通过compose编排启动的 时区问题!!!!解决了容器的时区问题!!!
变量值: 变量名 # 这些变量将会被直接写到镜像中的/etc/profile
command: [ #使用 command 可以覆盖容器启动后默认执行的命令
'--character-set-server=utf8mb4', #设置数据库表的数据集
'--collation-server=utf8mb4_unicode_ci', #设置数据库表的数据集
'--default-time-zone=+8:00' #设置mysql数据库的 时区问题!!!! 而不是设置容器的时区问题!!!!
]
server_name2: # 开始第二个容器
server_name:
stdin_open: true # 类似于docker run -d
tty: true # 类似于docker run -t
volumes: # 以上每个服务中挂载映射的目录都在这里写入一次,也叫作声明volume
test1:
test2:
test3:
networks: # 如果要指定ip网段,还是创建好在使用即可,声明networks
my_net:
driver: bridge # 指定网卡类型
myapp_net:
driver: bridge
例子
start.sh
#prepare image
docekr image pull redis
docker image build -t flask-demo
#create network
docker network create -d bridge demo-network
#create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_SERVENR=redis-server -p 5000:5000 flask-demo
docker-compose
version: "3.8"
services:
flask-demo:
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
练习
nginx
FROM nginx:1.21.0-alpine
ADD index.html /usr/share/nginx/html/index.html
docker run \
-p 9000:80 \
--name instructor \
--restart=always \
-v /home/data/nginx/instructor/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
-v /home/data/nginx/instructor/nginx/conf/conf.d:/etc/nginx/conf.d \
-v /home/data/nginx/instructor/nginx/log:/var/log/nginx \
-v /home/data/nginx/instructor/nginx/html:/usr/share/nginx/html \
-d nginx
docker run -d -it -p 8090:80 -v /home/demo:/usr/share/nginx/html --name demo nginx
docker run -d -it -p 9001:80 -v /home/data/nginx/admin/nginx:/usr/share/nginx/html --name admin nginx
docker run -d -it -p 80:80 -v /home/data/nginx/love/nginx:/usr/share/nginx/html -v /home/data/nginx/love/conf/nginx.conf:/etc/nginx/nginx.conf -v /home/data/nginx/love/cret:/var/cret --name love nginx
docker run -d -it -p 80:80 -v /home/data/nginx/christmas:/usr/share/nginx/html --name christmas nginx
docker run -d -it -p 80:80 -v /home/data/nginx/love/nginx:/usr/share/nginx/html -v /home/data/nginx/love/conf/hjzweb.conf:/etc/nginx/nginx.conf -v /home/data/nginx/love/cret:/var/cret --name love nginx
配置https
server {
listen 80;
server_name www.hjzweb.club;
rewrite ^(.*)$ https://$host$1;
location / {
index index.html index.htm;
}}
# HTTPS server
server {
listen 443 ssl;
server_name www.hjzweb.club;
root html;
index index.html index.htm;
ssl_certificate cert/hjzweb.club_bundle.pem;
ssl_certificate_key cert/hjzweb.club.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
server {
listen 80;
server_name www.hjzweb.club;
server_tokens off;
return 301 https://example.xyz$request_uri;
}
server {
listen 443 ssl http2;
server_name example.xyz;
server_tokens off;
ssl_certificate /etc/nginx/ssl/example.pem;
ssl_certificate_key /etc/nginx/ssl/example.key;
location / {
proxy_pass http://服务器ip地址:80;
}
}
ipinfo
基本的linux命令
apt-get update
apt-get install wget
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
mv ipinfo_2.0.1_linux_amd64.tar.gz /usr/bin/ipinfo
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
dockerfile
FROM ubuntu:21.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
每一行的Run命令都会生成一层image layer,导致镜像文件臃肿。
改进
FROM ubuntu:21.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz&& \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz&& \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo&& \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
mysql
#获取镜像
docker pull mysql:5.7
#创建容器
docker run -d -p 10020:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
#需要配置密码
-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置
/var/lib/mysql
mysql数据库的数据
创建8.0的版本:
$ mkdir -p /usr/mysql/conf /usr/mysql/data
$ chmod -R 755 /usr/mysql/
vim /usr/mysql/conf/my.cnf
[client]
#socket = /usr/mysql/mysqld.sock
default-character-set = utf8mb4
[mysqld]
#pid-file = /var/run/mysqld/mysqld.pid
#socket = /var/run/mysqld/mysqld.sock
#datadir = /var/lib/mysql
#socket = /usr/mysql/mysqld.sock
#pid-file = /usr/mysql/mysqld.pid
datadir = /usr/mysql/data
character_set_server = utf8mb4
collation_server = utf8mb4_bin
secure-file-priv= NULL
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# Custom config should go here
!includedir /etc/mysql/conf.d/
docker run --restart=unless-stopped -d --name mysql01 -v /usr/mysql/conf/my.cnf:/etc/mysql/my.cnf -v /usr/mysql/data:/var/lib/mysql -p 10020:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.16
tomcat
FROM centos
MAINTAINER hjz<haojingzhaoemail@qq.com>
COPY readme.txt /usr/local/readme.txt
ADD jdk-8u11-linux-64.tar.gz /usr/local/
ADD apache-tomcat-9.0.22.tar.gz /usr.local/
RUN yum -y install vim
ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apcahe-tomcat-9.0.22
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.22
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
EXPOSE 8080
CMD /usr/local/apache-tomcat-9.0.22/bin/startup.sh && tail -F /url/local/apache-tomcat-9.0.22/bin/logs/catalina.out
构建镜像:
docker build -f dockerfile -t diytomcat .
启动容器:
redis
分片+高可用+负载均衡
- 创建一个网卡
docker network create redis --subnet 172.38.0.0/16
#查看网络 详细配置信息
docker network inspect redis
#脚本创建六个redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.11
cluster-announce-port 6379
clister-announce-bus-port 16379
appendonly yes
EOF
done
docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
-v /mydata/redis/node-${port}/data:/data \
-v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.1${port} redis:5.0.9-aline3.11 redis-server /etc/redis/redis.conf; \
docker run -p 6371:6379 -p 16371:16379 --name redis-1 \
-v /mydata/redis/node-1/data:/data \
-v /mydata/redis/node-1/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
#进入redis容器
docker exec -it redis-1 /bin/sh
#创建redis 集群
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
#使用redis集群
redis-cli -c
#查看集群信息
cluster info
cluster nodes
python
FROM ubuntu:21.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
ADD hello.py /
CMD ["python3","/hello.py"]
- 添加hello.py 文件到根\目录
FROM buildpack-deps:jessie
# 删除旧的 python
RUN apt-get purge -y python.*
# 使用 UTF-8 编码
ENV LANG C.UTF-8
ENV PYTHON_VERSION 3.6.5
# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
ENV PYTHON_PIP_VERSION 10.0.1
RUN set -ex \
&& curl -fSL "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" -o python.tar.xz \
&& mkdir -p /usr/src/python \
&& tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
&& rm python.tar.xz \
\
&& cd /usr/src/python \
&& ./configure --enable-shared --enable-unicode=ucs4 \
&& make -j$(nproc) \
&& make install \
&& ldconfig \
&& pip3 install --no-cache-dir --upgrade --ignore-installed pip==$PYTHON_PIP_VERSION \
&& find /usr/local -depth \
\( \
\( -type d -a -name test -o -name tests \) \
-o \
\( -type f -a -name '*.pyc' -o -name '*.pyo' \) \
\) -exec rm -rf '{}' + \
&& rm -rf /usr/src/python ~/.cache
# make some useful symlinks that are expected to exist
RUN cd /usr/local/bin \
&& ln -s easy_install-3.5 easy_install \
&& ln -s idle3 idle \
&& ln -s pydoc3 pydoc \
&& ln -s python3 python \
&& ln -s python3-config python-config
CMD ["python3"]
Golang
docker pull golang
docker run -it golang /bin/bash
go env -w GO111MODULE=on #开启go mod
go env -w GOPROXY="https://goproxy.cn"
go env -w GOPATH="/go" #如果本身就是,那就不需要再改了,可以省略
docker commit 2d5 golang-local
docker run -d -it --name yiqing -p 8081:8081 -v /home/data/go:/go golang-local
chatGPT
docker run -itd --name wechatbot --restart=always \
-e APIKEY=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 \
-e AUTO_PASS=false \
-e SESSION_TIMEOUT=60s \
-e MODEL=text-davinci-003 \
-e MAX_TOKENS=512 \
-e TEMPREATURE=0.9 \
-e REPLY_PREFIX=我是来自机器人回复: \
-e SESSION_CLEAR_TOKEN=下一个问题 \
docker.mirrors.sjtug.sjtu.edu.cn/qingshui869413421/wechatbot:latest
进入 --
tail -f -n 50 /app/run.log
lantern
docker pull kent72/lantern
docker run -d -p 8787:8787 kent72/lantern
lantern.env
HTTP_PROXY=192.168.1.111:8787
HTTPS_PROXY=192.168.1.111:8787
NO_PROXY="localhost,127.0.0.1,*.local,/var/run/docker.sock"
http_proxy=192.168.1.111:8787
https_proxy=192.168.1.111:8787
no_proxy="localhost,127.0.0.1,*.local,/var/run/docker.sock"
docker run -d --env-file /root/lantern.env --name ubuntu ubuntu bash
例子
docker run -itd --env-file /home/lantern/lantern.env --name wechatbot --restart=always \
-e APIKEY=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 \
-e AUTO_PASS=false \
-e SESSION_TIMEOUT=60s \
-e MODEL=text-davinci-003 \
-e MAX_TOKENS=512 \
-e TEMPREATURE=0.9 \
-e REPLY_PREFIX=我是来自机器人回复: \
-e SESSION_CLEAR_TOKEN=下一个问题 \
docker.mirrors.sjtug.sjtu.edu.cn/qingshui869413421/wechatbot:latest
xxnet
docker run -itd \
--rm \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
--privileged \
--name=xxnet \
-p "8085:8085" \
-p "8086:8086" \
-p "8087:8087" \
-p "1080:1080" \
-v "$(pwd)/conf/supervisord/supervisord.conf:/etc/supervisord.conf" \
-v "$(pwd)/conf/xxnet/gae_proxy/manual.ini:/data/xxnet/data/gae_proxy/manual.ini" \
-v "$(pwd)/conf/xxnet/launcher/config.yaml:/data/xxnet/data/launcher/config.yaml" \
-v "$(pwd)/conf/xxnet/x_tunnel/client.json:/data/xxnet/data/x_tunnel/client.json" \
xxnet
例子
docker run -itd \
--rm \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
--privileged \
--name=xxnet \
-p "8085:8085" \
-p "8086:8086" \
-p "8087:8087" \
-p "1080:1080" \
codezm/xxnet
知识
docker container run 背后发生什么
docker container run -d --publish 80:80 --name webhost nginx
- 在本地查找是否有nginx这个image镜像,但是没有发现
- 去远程的image registry查找nginx镜像(默认的registry是Docker Hub)
- 下载最新版本的nginx镜像(nginx:latest默认)
- 基于nginx镜像来创建一个新的容器,并准备运行
- 在宿主机上打开80端口并把容器的80端口转发到宿主机上
- 启动容器,运行指定的命令(这里是一个shell脚本去启动nginx)
聊一聊scratch镜像
编译c语言文件:
gcc --static -o hello hello.c
FROM scratch
ADD hello /
CMD ["/hello"]
查看镜像的分层
docker image history 镜像的名字/ID
技巧
快速停止/删除
docker stop $(docker ps -aq)
docker rm -f $(docker ps -aq)
过滤查看nginx的地址
docker container run -d --rm --name web -p 8080:80 nginx
#查看容器的地址
docker container inspect --format '{{.NetworkSettings.IPAddress}}' web
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?