Docker是什么?Kubernetes又是什么?如何通过代码实战理解它们?

嗨,你好呀,我是猿java

云原生时代,作为技术人员,如果不了解 Docker 和 Kubernetes,那绝对是技术栈上的一个短板。那么,什么是 Docker?什么又是 Kubernetes?它们之间存在怎样的关系? 这篇文章,我将通过理论加代码实战的方式,详细地剖析他们。

1. 原理部分

1.1 Docker

Docker是一个容器化平台,它允许开发者将应用及其依赖项打包到一个称为容器的标准化单元中,以便可以在不同环境中快速、可靠地运行这些应用。它的本质是利用操作系统的CgroupsNamespace机制创建出来的隔离环境。更多详情参考我以往的文章:容器 = Namespace + Cgroups + rootfs

Docker核心概念包括以下几个:

1.1.1 Kubernetes核心概念

1. 镜像:Docker(Image)镜像是一个轻量级、独立、可执行的软件包,其中包含运行某个应用程序所需的所有内容,包括代码、运行时、库、环境变量和配置文件。镜像是不可变的,基于镜像可以创建容器。

2. 容器:容器(Container)是镜像的运行实例,它是一个轻量级、独立的环境,可以在任何支持 Docker的机器上运行。容器与主机系统共享操作系统内核,但在进程空间上是隔离的。

3. Dockerfile:Dockerfile是一个文本文件,包含构建 Docker镜像的所有命令,可以通过 Dockerfile来自动化镜像的创建过程。

4. Docker Hub:Docker Hub是一个云端的 Docker镜像存储库,用户可以在上面发布和获取镜像,它提供了一个平台来共享和管理镜像。

5. 容器编排:Docker Compose是一种定义和运行多容器 Docker应用程序的工具,通过一个docker-compose.yml文件,用户可以定义一个应用包含的所有服务,并使用简单的命令启动或停止它们。

Docker容器可以描述成以下全景图(图片来自极客张磊):

img.png

1.1.2 Docker的优点

Docker的优点可以用一句话总结:一次打包,到处部署。这个和 JVM一次编译,到处运行的设计有着异曲同工之妙,所以说,计算机领域很多思维是相通的!

下图对比了使用与不使用 Docker的场景:

img.png

  • 不使用 Docker:每次将应用部署新主机时,都需要重新安装一遍应用需要的环境,因为操作系统多,运营商可能也不同,安装环境是一件痛苦的事情。
  • 使用 Docker:制作 Docker镜像时,已经包含应用及其所需的环境,因此可以到任何运营商提供的操作系统上运行。

1.2 Kubernetes

Kubernetes(K8s) 一词源自希腊,意为“舵手”或“掌舵者”,用于指导和管理船只的航行。它是一个容器编排平台,负责自动化容器的部署、扩展(水平扩展)、负载均衡、以及故障恢复等工作。

Kubernetes的核心概念包括以下几个:

1.2.1 Kubernetes核心概念

1. 节点:节点(Node)是 Kubernetes集群中的一台物理机或虚拟机,负责运行应用程序的工作负载。每个节点运行一个Kubelet(用于管理节点上的容器)和一个容器运行时(例如Docker)。

2. Pod:Pod是 Kubernetes中最小的可部署单元,通常包含一个或多个容器。Pod中的容器共享网络和存储,可以协同工作。

3. 控制器:控制器(Controller)负责管理和维护集群中的Pod和相关的服务。常见的控制器有 ReplicaSet(确保指定数量的Pod副本在运行)、Deployment(管理无状态应用)和 StatefulSet(管理有状态应用)。

4. 服务:服务(Service)是一个抽象的方式,用于定义一组Pod的逻辑集合以及访问这些 Pod的策略。Kubernetes中的服务提供了负载均衡和服务发现功能。

5. 命名空间:命名空间(Namespace)用于在同一个 Kubernetes集群中将资源进行逻辑上的隔离。它允许多个团队或项目共享一个集群而不会相互影响。

6. 配置管理:配置管理包含 ConfigMap 和 Secret。ConfigMap 用于存储非机密的数据,类似配置文件。Secret用于存储机密数据,如密码、OAuth令牌和SSH密钥。

7. Ingress:Ingress是一个 API对象,管理外部访问到集群中服务的 HTTP和 HTTPS路由。它提供负载均衡、SSL终止和基于名称的虚拟托管等功能。

8. Deployment:Deployment是一个用于管理无状态应用程序的核心组件。它提供了一种声明式的方法来管理 Pod和 ReplicaSet,从而实现应用程序的部署、升级和扩缩。

Kubernetes可以描述成以下全景图(图片来自极客张磊):

img.png

1.2.2 Kubernetes 的工作流程

Kubernetes 的整体工作流程可以总结成下面 6个步骤:

  1. 用户定义和提交文件:用户通过 kubectl提交描述文件,例如 Deployment 和 Service 定义,这些文件描述了应用副本数量、负责选择负载均衡的标签、资源需求等信息。
  2. API Server 接受请求:API Server处理用户的对象创建请求,这些请求会被记录在 etcd 中。
  3. 调度器决定 Pod 的位置:Scheduler根据集群的整体资源使用情况、节点健康状况、Pod 的资源要求等调度策略,将 Pod安排到集群中的某个 Node上。
  4. Kubelet 处理并运行Pod:在选定的 Node上,Kubelet调用 Container Runtime(例如 Docker 或 Container)启动容器,并根据请求的规格拉取镜像、分配资源、启动应用。
  5. Kube Proxy 实现服务访问:集群中的 Kube Proxy负责为每个 Pod生成虚拟 IP,通过这些 IP,集群内外部的访问可以通过 Kubernetes Services 来访问这些 Pod,进行负载均衡。
  6. 状态保持和自动修复:Kubernetes的控制器会持续监控 Pod和节点的状态,并在检测到某些 Pod不可用或节点失效时,自动重启或调度新的 Pod到健康的节点上。

整个流程如下图:
img.png

1.3 两者关系

Docker提供容器化机制,用于封装和打包应用,并利用 CI/CD管道将容器镜像推送至中央容器注册中心(如 Docker Hub 或 Harbor),而 Kubernetes是一个强大的容器编排平台,负责调度、扩展和管理 Docker容器化应用,保证系统高可用、故障自动恢复。

从宏观上看,两者关系可以通过下图来形象的表达:

img.png


好了,理论部分讲解完后,我们正式进入代码实战部分,俗话说:说起来容易做起来难,对于 Kubernetes集群还真有点难度,来,上干货!!!

2. 代码实战

2.1 目的

开发一个简单的 Java Web应用,并将其 Docker容器化部署到 Kubernetes集群中,然后在浏览器访问,展示:Hello world, This is my docker running in Kubernetes!

2.2 步骤

整体流程核心步骤有下面 4个:

  1. 安装环境
  2. 创建 Web应用
  3. 应用 Docker 容器化
  4. 部署到 Kubernetes 集群
  5. 浏览器验证

2.2.1 安装环境

首先,需要确保我们安装了以下环境:JDK(Java Development Kit), Gradle(Maven),Docker, Kubernetes,下面是我安装的版本:

JDK 版本:
img.png

Gradle版本:
img.png

Docker 版本:
我使用的是 Mac Pro 电脑,直接使用 brew安装:

1
brew install --cask docker

img.png
img.png

Kubernetes 版本:

我使用的是 Mac Pro电脑,直接使用 brew安装:

1
brew install kubectl

img.png

注意:Gradle 和 Maven 是项目管理工具,只要安装一个就OK,自己熟悉哪个就安装哪个。

2.2.2 创建Java Web应用

1. 创建Gradle(Maven)项目

创建一个 Springboot 项目,整个目录结构如下:

img.png

src/main/cloud/com/yuanjava/docker目录下创建一个新的 Java类,如HelloController.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yuanjava.docker;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 猿java
*/
@RestController
public class HelloController {
@GetMapping("/test")
public String test() {
return "Hello world, This is my docker running in Kubernetes!";
}
}

2. Gradle(Maven)打包项目:

Gradle打包指令如下:

1
gradle build

这里在 build/libs 目录下就就会看到打包后的 jar包:
img.png

2.2.3 容器化 Java Web应用

1. 编写Dockerfile:

在项目根目录下创建一个Dockerfile:

1
2
3
FROM openjdk:17-jdk-slim
COPY build/libs/yuanjava-1.0.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

2. 构建Docker镜像:

在当前目录,运行以下命令就可以构建 Docker镜像,-t的作用是给这个镜像加一个Tag,也就是起一个好听的名字。

1
docker build -t yuanjava:1.0 .

img.png

运行完上面的指令之后,就可以在 Docker镜像库看到刚才打的镜像了,可以使用docker images指令查看镜像:

1
docker images

也可以通过 Docker的可视化工具查看镜像:
img.png

3. 运行Docker容器

我们可以通过下面的命令来启动 Docker容器:

1
docker run -p 9999:9999 yuanjava:1.0

然后,在浏览器中访问http://localhost:9999/test来测试 Docker是否启动完成。访问结果如下图:

img.png

4. 推送 Docker镜像到远程仓库

这一步是为了下面将 Docker镜像部署到 Kubernetes集群做准备,推送命令如下:

1
docker push yuanjava:1.0

img.png

推送完之后,远程仓库就多了一个镜像,如下图所示:

img.png

注意:如果没有 Docker账号,需要先创建账号。

2.2.4 部署到Kubernetes集群

1. 启动 Kubernetes集群

通常来说,我们会使用minikube命令来启动和管理 Kubernetes 集群,安装和启动命令如下:

1
2
3
4
5
6
7
# 安装 minikube
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
chmod +x minikube
sudo mv minikube /usr/local/bin/

# 启动 Kubernetes集群
minikube start

img.png

2. 编写Kubernetes Deployment配置

创建一个deployment.yaml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1 # 定义了 Kubernetes API 的版本
kind: Deployment # 声明这个 Kubernetes 资源的类型
metadata:
name: yuanjava # 包含资源的元数据,Kubernetes 通过 metadata 字段中的各类信息来跟踪和管理资源
spec:
replicas: 2 # Pod的副本数量,即 Deployment将运行 2个相同的 Pod
selector:
matchLabels:
app: yuanjava # 告诉 Deployment 如何找到它管理的 Pod
template:
metadata:
labels:
app: yuanjava # 定义 Pod 的模板
spec:
containers:
- name: yuanjava
image: yuanjava/yuanjava:1.0 #定义容器使用的镜像,Kubernetes 会从容器镜像仓库中拉取它
ports:
- containerPort: 9999 # 指定容器内部监听的应用端口

3. 编写Kubernetes Service配置

创建一个service.yaml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service # 用于描述和管理向外部或者内部暴露应用的网络访问方式
metadata:
name: yuanjava # 包含资源的元数据信息
spec:
type: LoadBalancer # 定义了服务的类型,LoadBalancer 类型会将服务暴露给外部网络
ports:
- port: 80 # 这是对外暴露的端口,表示外部客户端访问服务时使用的端口号
targetPort: 9999 # 这是集群内部目标 Pod 上运行的容器所监听的端口
selector:
app: yuanjava

4. 部署到Kubernetes

我们可以使用kubectl命令应用配置,命令说明如下:

kubectl apply -f deployment.yaml:根据 deployment.yaml 文件中的定义,创建或更新 Kubernetes中相应的 Deployment。
kubectl apply -f service.yaml:根据 service.yaml 文件中的内容,Kubernetes会创建一个 Service,并将其与运行 Deployment 中的 Pod关联起来。

1
2
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

img.png

5. 验证部署

使用以下命令检查 Pods和 Service状态:

1
2
kubectl get pods
kubectl get services

如下图,2个 Pod都处于 Running 状态:
img.png

2.2.5 浏览器验证

最后,我们可以在浏览器中访问,验证应用是否成功部署到 Kubernetes 集群,访问地址是:http://EXTERNAL-IP/hello ,如下图:

img.png

因为,我是本地部署,所以EXTERNAL-IP地址为<pending>,即未分配,但是可以通过minikube service yuanjava查看 IP地址,如下图所示:

img.png

最后,打开地址http://192.168.49.2:63213/test, 见证奇迹的时刻到了:

img.png

因为是本地环境,所以链接最终会跳转到http://127.0.0.1:63213/test

到此,我们就成功地手动将 Docker镜像部署到 Kubernetes集群,并且通过 URL能够访问,过程很艰辛,结果还是比较美好。

但是,如果要部署大量的机器,这样手动操作肯定是不行,因此,我们需要结组一些 CI/CD工具(如 GitHub Actions、GitLab CI/CD 或 Jenkins)实现自动化 Docker镜像构建和 Kubernetes 部署。

这里以 GitHub Actions为例,创建 .github/workflows/auto-deploy.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: Auto Deploy Application to Kubernetes

on:
push:
branches:
- main

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# Step 1: 检出代码
- name: Checkout code
uses: actions/checkout@v3

# Step 2: 登录 Docker Registry
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

# Step 3: 构建并推送 Docker 镜像
- name: Build and push Docker image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/yuanjava:latest .
docker push ${{ secrets.DOCKER_USERNAME }}/yuanjava:latest

# Step 4: 设置 kubectl
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'

# Step 5: 配置 kubeconfig 文件
- name: Configure kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig
export KUBECONFIG=kubeconfig

# Step 6: 部署到 Kubernetes
- name: Apply Kubernetes manifests
run: |
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

关键说明:

  • 配置里面的 DOCKER_USERNAME, DOCKER_PASSWORD, KUBECONFIG,需在项目的 Settings > Secrets 中配置。
  • 在触发阶段(push 到 main 分支)时,上述流水线将自动完成 Docker 构建、推送,并将应用部署到 Kubernetes。

3. 总结

本文,我们先从理论上分析了 Docker 和 Kubernetes的核心概念以及它们之间的关系。

接着,我们从代码实战的角度,带大家一步一步实操了如何编写一个简单的 Java Web应用,并将其 Docker容器化,最后部署到 Kubernetes集群中,然后在浏览器访问。在实操的期间,因为所有的环境都是本地环境临时搭建,所以遇到了很多的问题,但最终还是成功了。

通过这次实操,再次说明了做技术不能只停留在理论,实操很重要,实操期间我们可能遇到很多问题,但是,当我们通过各种方式去解决问题的时候,这个过程其实就是对技术更深入的学习和掌握。

个人建议:

Docker是云原生很重要相对简单的一个技术基础,强烈建议掌握并且一定要去实战。Kubernetes的难度系统会比 Docker大,它为大量容器提供调度、资源管理、弹性伸缩等功能,如果使用的机器数量比较少,Kubernetes其实很难用上,但是,如果可以,还是建议我们创造条件在实际生产环境中去使用和学习 Kubernetes。相信我,这种有压力的学习效果贼棒!

4. 参考资料

kubernetes官网
docker官网

5. 交流学习

最后,把猿哥的座右铭送给你:投资自己才是最大的财富。 如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing