SpringBoot应用:Docker与Kubernetes全栈实战秘籍
文章目录
- 什么是docker?
- 为什么要用docker?
- docker有什么?
- 什么是Kubernetes?
- 怎么安装docker
- 第一步,下载安装包
- 第二步,准备环境
- 第三步,安装
- 第四步,注册账号
- 第五步,验证是否可以使用
- 第六步,配置国内镜像源
- 怎么安装Kubernetes?
- 第一步,找到对应的版本
- 第二步,下载k8s
- 第三步,安装Kubernetes
- 安装kubectl
- 直接下载:
- 使用 curl:
- 编写web应用
- 第一步,新建一个springboot项目
- 第二步,申请api-key
- 第三步,编写yml配置文件
- 第四步,新建一个web控制层
- 第五步,新建service
- 第六步,新建前端页面
- 第七步,访问web页面
- 部署到docker
- 第一步,编写Dockerfile文件
- 第二步,打包jar包
- 第三步,构建镜像
- 第四步,启动镜像
- 部署Kubernetes
- `deployment.yaml`
- `service.yaml`
- 第一步,编写deployment.yaml
- 第二步,编写service.yaml
- 第三步,构建pod
- 第四步,验证
- 总结
什么是docker?
首先,咱们得了解一下什么是docker。它其实就是一种开源的平台,用来自动部署、管理、扩展和运行各种应用程序的技术。它允许开发人员把他们编写的应用程序和相关的依赖库,全都封装到一个叫做"容器"的小家伙里面,然后就能在任何只要装了Docker的环境里跑这个应用!Docker有很多牛逼的功能,例如流畅的跨平台兼容性、严格的资源隔离、精确的版本控制以及超高的资源利用率等等!
为什么要用docker?
接下来,科普一些Docker的背景知识,首先,docker之所以能诞生,主要是因为传统的应用部署方式面临着以下几个难题:
- 旧有的应用部署手段:时间回到Docker刚刚面世的那会儿,应用程序的部署往往依赖于特定的操作系统环境配置和依赖项。然而,这种依赖性容易引发各种不统一的问题,无论是开发环境和生产环境之间的差异,还是依赖项之间的冲突,都可能造成应用程序在别的环境下“水土不服”,无法好好运转。
- 传统虚拟化技术的短板:以前我们常用的虚拟化技术,比如说VMWare或者VirtualBox,虽然能在一台物理机器上跑出好几个虚拟机,每个虚拟机都有自己独立的操作系统。但是,虚拟机通常比较重,启动和运行都要消耗大量的资源,导致性能损耗严重。
- 微服务架构的崛起:近年来,微服务架构逐渐成为了主流,应用程序被拆分成了许多个独立的小服务。而这些小服务则需要在各种各样的环境中快速进行部署和伸缩。如果还是采用传统的部署方式,就很难满足微服务灵活性和扩展性的要求了。
docker有什么?
图片来自csdn@小生凡一
镜像就像是一个应用程序的“模板”。它包含了运行应用所需的一切,比如操作系统、库和应用代码。你可以把它想象成一个打包好的应用快照,随时可以用来创建容器。
容器是镜像的实际运行实例。你可以把它当成是根据模板(镜像)生产出来的一个具体的产品。容器是轻量级的,启动速度快,而且每个容器都有自己独立的运行环境。比如,你可以用同一个镜像启动多个容器,每个容器里跑的应用都是独立的。
仓库是用来存储和共享镜像的地方。你可以把它理解为一个镜像的“库”,在这里你可以找到别人上传的镜像,也可以把自己创建的镜像上传到这里。Docker Hub 是最常用的公共仓库,但你也可以设置自己的私有仓库。
什么是Kubernetes?
Kubernetes(简称 K8s)是一个开源的容器编排系统,用于自动化容器化应用程序的部署、扩展和管理。Kubernetes 提供了一种声明式的配置模型,使得用户可以定义应用程序的期望状态,Kubernetes 会自动确保系统始终处于该状态。
Docker 负责创建和管理容器,Kubernetes 负责管理和调度这些容器,他们的关系犹如这样:
例如在java微服务的开发过程中,我们要新增这个微服务的节点,如果没用docker,我们是不是要把jar包上传到服务器,然后再把它启动起来,一套流程下来很麻烦,但是你用了docker配合k8s之后,你只需要点一个加号键,就新增了一个节点(pod),点两次就新增了两个,就像这样:
怎么安装docker
第一步,下载安装包
在Windows中docker有个桌面程序叫做Docker Desktop,这个是在mac和Windows上面独有的程序,在Linux上面是没有的,所以我们点击下载之后,我们会得到一个exe安装包
官网地址https://www.docker.com/
第二步,准备环境
先打开两个设置,控制面板》程序》启用或关闭windows功能》图中倒数第二和第三个点击勾选》重启电脑即可。
到本文发布截止时间,官网的exe包是4.33.1的版本,如果你的电脑太老了,这个时候就会出现下面的情况,这个时候我们需要安装4.24.1以下的版本。
这里我们选的是4.24.0版本的
第三步,安装
一路狂点下一步,页面叫我们注销一下以启用docker,我们按照他的提示执行就行了
第四步,注册账号
上面我们安装完成并且重启之后,进入这个软件,他会让我们注册一个账户,如果我们没有就可以先跳过这个环境以后注册也是可以的
注册这个账号的目的就是可以推送你自己的镜像到docker的中央仓库(和maven的中央仓库差不多),如果后面我们需要注册一个账号的话,点击右上角的sign in浏览器自动就会跳出页面,如果我们需要注册我们就点击sign up,之后会让我们输入邮箱,用户名和密码
第五步,验证是否可以使用
登录/跳过登录之后 这个时候我们看左下角显示的是engine running,意思就是docker以及启动起来了
之后我们用cmd运行docker images
命令,出现下面的情况就算成功安装了
第六步,配置国内镜像源
docker的默认仓库也是在国外,所以国内有时候甚至经常pull拉不了镜像,就像github一样,所以我们需要配置国内的镜像库,但是前段时候听说docker镜像很多不能用了,但是博主还是找了一个目前可以用的镜像库,有可能以后要经常换,这个是暂时没有办法的
在docker右上角的设置里面选择docker engine(释义:引擎,发动机;机车,火车头;工具),加入蓝色框框框住的文字,下面已贴出
,"registry-mirrors": ["https://docker.m.daocloud.io"]
注意有个逗号,之后右下角的 Apply & restart 就会亮起,我们点击等他重启即可,之后我们cmd 输入docker info
就会出现我们刚刚配置的镜像。
怎么安装Kubernetes?
第一步,找到对应的版本
第二步,下载k8s
https://github.com/AliyunContainerService/k8s-for-docker-desktop
选择好对应的版本分支,进行源码下载,解压好之后你会得到这样的一个文件夹
第三步,安装Kubernetes
“以管理员身份运行” 的 PowerShell 中执行 Set-ExecutionPolicy RemoteSigned
命令。
在Windows上,使用 PowerShell执行
.\load_images.ps1
执行完成之后打开docker desktop,选择图中所选的,然后重启docker desktop 等待即可。安装启动完成后,即可在左下角看到docker engine 和 Kubernetes 是蓝色的运行标志
安装kubectl
kubectl
是 Kubernetes 的命令行工具,用于与 Kubernetes API 服务器进行交互。通过 kubectl
,用户可以执行各种操作,如创建、查看、更新和删除 Kubernetes 资源。
按照道理来说,Windows 版的 Docker Desktop 有自带版本的 kubectl
,但是如果你在cmd命令行执行下面命令,没有信息打印出来可以安装一下
kubectl version --client
直接下载:
通过访问 Kubernetes 发布页面 直接下载你对应的k8s版本。 请务必选择适用于你的体系结构的二进制文件(例如,amd64、arm64 等)。Windows下载exe,一路安装即可
使用 curl:
如果你已安装 curl
,可以使用以下命令:
curl.exe -LO "https://dl.k8s.io/release/v1.29.2/bin/windows/am
编写web应用
我们这里编写一个简单的web应用(AI对话,一问一答),最终的效果是前端可以直接访问,并且可以通过页面输出你的问题
第一步,新建一个springboot项目
SpringBoot入门:如何新建SpringBoot项目(保姆级教程)
我们的pom文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><groupId>com.masiyi</groupId><artifactId>spring-ai</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-ai</name><description>spring-ai</description><properties><java.version>23</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.12</spring-boot.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-ai</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2023.0.1.2</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories>
</project>
如果不配置仓库地址,则会出现包拉不下来的情况,如果jdk版本不对也会出现编译不了的情况,这个pom里面我们使用的jdk编译版本为17版本,但是spring-cloud-starter-alibaba-ai的jdk编译版本为17,所以我们使用比17高的jdk版本就行了
第二步,申请api-key
阿里的apikey是需要付费的,所以我们通过https://bailian.console.aliyun.com/
购买对应的服务之后就可以申请我们的api-key
第三步,编写yml配置文件
server:port: 8999spring:application:name: tongyi-examplecloud:ai:tongyi:connection:api-key: sk-6670e214d0414bdbad173b7xxxx
我们按照上面配置好,如果是2023.0.1.2版本就按照上面写,官网的文档估计没有更新到最新版本,这部分还是作者看源码才知道要这么配置的。
配置好之后我们就可以来上手spring-cloud-starter-alibaba-ai了
第四步,新建一个web控制层
package com.masiyi.springai.controller;/** Copyright 2023-2024 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import com.masiyi.springai.service.TongYiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.time.Duration;
import java.time.LocalTime;
import java.util.Map;/*** TongYi models Spring Cloud Alibaba Controller.** @author yuluo* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>* @since 2023.0.0.0*/@RestController
@RequestMapping("/ai")
@CrossOrigin
public class TongYiController {@Autowired@Qualifier("tongYiSimpleServiceImpl")private TongYiService tongYiSimpleService;@GetMapping("/example")public String completion(@RequestParam(value = "message", defaultValue = "Tell me a joke")String message) {return tongYiSimpleService.completion(message);}@GetMapping("/stream")public Map<String, String> streamCompletion(@RequestParam(value = "message", defaultValue = "请告诉我西红柿炖牛腩怎么做?")String message) {return tongYiSimpleService.streamCompletion(message);}@PostMapping(value = "/flux-demo", produces = "text/event-stream; charset=utf-8")public Flux<ServerSentEvent> hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {return Flux.interval(Duration.ofSeconds(1)) // 每隔一秒发送一个事件.take(10) // 限制发送 10 次.map(seq -> {String message = String.format("Hello %s, here is your message #%d at %s",name, seq + 1, LocalTime.now());return ServerSentEvent.<String>builder().id(String.valueOf(seq)) // 设置事件 ID.event("chat-message") // 自定义事件名称.data(message) // 传递的消息数据.build();});}@PostMapping(value = "/flux", produces = "text/event-stream; charset=utf-8")public Flux<ServerSentEvent> flux(@RequestParam(name = "message", defaultValue = "你好") String message) {return tongYiSimpleService.flux(message);}}
这里面的有两个方法,一个是返回一个字符串,一个是返回一个steam流,但是官网的写法后面还需要改造一下,这里留个坑,就不做研究。
第五步,新建service
package com.masiyi.springai.service.impl;/** Copyright 2023-2024 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
import com.masiyi.springai.service.AbstractTongYiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;import java.util.Map;/*** The Chat simple example service implementation.* There is optional message parameter whose default value is "Tell me a joke".* pl The response to the request is from the TongYi models Service.** @author yuluo* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>* @since 2023.0.0.0*/@Service
public class TongYiSimpleServiceImpl extends AbstractTongYiServiceImpl {private static final Logger logger = LoggerFactory.getLogger(TongYiSimpleServiceImpl.class);private final ChatModel chatModel;private final StreamingChatModel streamingChatModel;@Autowiredpublic TongYiSimpleServiceImpl(ChatModel chatModel, StreamingChatModel streamingChatModel) {this.chatModel = chatModel;this.streamingChatModel = streamingChatModel;}@Overridepublic String completion(String message) {Prompt prompt = new Prompt(new UserMessage(message));return chatModel.call(prompt).getResult().getOutput().getContent();}@Overridepublic Map<String, String> streamCompletion(String message) {StringBuilder fullContent = new StringBuilder();streamingChatModel.stream(new Prompt(message)).flatMap(chatResponse -> Flux.fromIterable(chatResponse.getResults())).map(content -> content.getOutput().getContent()).doOnNext(fullContent::append).last().map(lastContent -> Map.of(message, fullContent.toString())).block();logger.info(fullContent.toString());return Map.of(message, fullContent.toString());}@Overridepublic Flux<ServerSentEvent> flux(String message) {TongYiChatOptions chatOptions = new TongYiChatOptions();chatOptions.setIncrementalOutput(true);Flux<ServerSentEvent> streamEvents = streamingChatModel.stream(new Prompt(message, chatOptions)).map(outputContent -> {String content = outputContent.getResult().getOutput().getContent();System.out.println(content);return ServerSentEvent.builder().event("message").data(content).build();});// 返回 Flux<ServerSentEvent>return streamEvents;}
}
第六步,新建前端页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Chat SSE Demo</title>
</head>
<body><h1>Server-Sent Events with POST</h1><div id="messages"></div><script>// 获取 URL 中的查询参数function getQueryParam(param) {const urlParams = new URLSearchParams(window.location.search);return urlParams.get(param);}function startSSE() {const url = 'http://localhost:8999/ai/flux'; // 后端服务器的完整 URLconst message = getQueryParam('message') || ''; // 获取查询参数中的 'message' 值if (!message) {displayMessage('No message provided in the URL');return;}fetch(url, {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded','Accept': 'text/event-stream' // 接受 SSE 类型的数据流},body: new URLSearchParams({'message': message})}).then(response => {if (!response.ok) {throw new Error('Network response was not ok');}const reader = response.body.getReader();const decoder = new TextDecoder();let buffer = ''; // 用于缓存不完整的数据let accumulatedData = ''; // 用于累计所有 data 内容return new ReadableStream({start(controller) {function read() {reader.read().then(({ done, value }) => {if (done) {controller.close();return;}buffer += decoder.decode(value, { stream: true });// 检查是否有完整的事件块(每个事件块之间有两个换行符)const eventBlocks = buffer.split("\n\n");// 将最后一个块留在缓存里,可能是不完整的buffer = eventBlocks.pop();eventBlocks.forEach(eventBlock => {const lines = eventBlock.split("\n");let eventData = { data: '' };lines.forEach(line => {if (line.startsWith("data:")) {// 累加所有的 data 行,不插入换行符eventData.data += line.substring(5).trim();}});// 仅当有数据时才显示if (eventData.data) {accumulatedData += eventData.data; // 累加数据displayMessage(accumulatedData); // 显示完整数据}});read();});}read();}});}).catch(error => {console.error('There has been a problem with your fetch operation:', error);});}function displayMessage(message) {const messageDiv = document.getElementById('messages');messageDiv.innerHTML = ''; // 清空之前的内容const newMessage = document.createElement('p');newMessage.innerHTML = message; // 显示累计后的数据messageDiv.appendChild(newMessage);}// 开始 SSE 连接startSSE();
</script></body>
</html>
在resource的static下面新建index2.html 文件,内容如上
第七步,访问web页面
http://localhost:8999/index2.html?message=你好,介绍一下你自己
这样,我们的web应用就写好了
部署到docker
第一步,编写Dockerfile文件
FROM openjdk:22-slim
COPY spring-ai-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
- FROM openjdk:22-slim
- 这一行指定了基础镜像。
openjdk:22-slim
是一个基于 Debian 的轻量级镜像,包含了 OpenJDK 22。slim
版本比标准版本更小,适合生产环境使用。FROM 里面的jdk版本比项目中的大就行了(项目中17)
- 这一行指定了基础镜像。
- COPY spring-ai-0.0.1-SNAPSHOT.jar app.jar
- 这一行将本地文件系统中的
spring-ai-0.0.1-SNAPSHOT.jar
文件复制到 Docker 镜像的根目录下,并重命名为app.jar
。
- 这一行将本地文件系统中的
- ENTRYPOINT [“java”,“-jar”,“/app.jar”]
- 这一行设置了容器启动时的默认命令。
["java", "-jar", "/app.jar"]
表示使用 Java 运行/app.jar
文件。ENTRYPOINT
指令确保即使在命令行中传递其他参数,这些参数也会作为参数传递给java -jar /app.jar
。
- 这一行设置了容器启动时的默认命令。
第二步,打包jar包
直接上手就是一个 mvn clean package
,然后将打出来的jar放到和Dockerfile 同目录下面,就像这样
第三步,构建镜像
docker build -t aijava:1.0 .
当你运行上述命令时,Docker 会执行以下步骤:
- 读取 Dockerfile:Docker 会读取当前目录下的 Dockerfile。
- 构建镜像层:
FROM openjdk:22-slim
:基于openjdk:22-slim
镜像创建一个新的镜像层。COPY spring-ai-0.0.1-SNAPSHOT.jar app.jar
:将当前目录下的spring-ai-0.0.1-SNAPSHOT.jar
文件复制到新镜像的根目录,并重命名为app.jar
。ENTRYPOINT ["java","-jar","/app.jar"]
:设置容器启动时的默认命令,即运行/app.jar
文件。
- 标记镜像:构建完成后,Docker 会将新镜像标记为
aijava:1.0
。
第四步,启动镜像
运行一个 Docker 容器:
docker run -d -p 8999:8999 aijava:1.0
docker run
:这是运行 Docker 容器的命令。-d
:表示在后台运行容器。-p 8999:8999
:将宿主机的 8999 端口映射到容器的 8999 端口。aijava:1.0
:这是要运行的镜像名称和标签。
然后我们就可以在浏览器验证这个容器了,如图就是没问题的!!
部署Kubernetes
部署之前我们先认识一下两个文件:
deployment.yaml
和 service.yaml
文件是 Kubernetes 中常用的两个配置文件,分别用于定义应用程序的部署和网络访问方式。
deployment.yaml
deployment.yaml
文件用于定义一个 Kubernetes Deployment 资源。Deployment 是一种控制器,用于管理应用程序的副本集和滚动更新,确保应用程序的高可用性和负载均衡。
service.yaml
service.yaml
文件用于定义一个 Kubernetes Service 资源。Service 是一种抽象,用于定义应用程序的网络访问方式,确保应用程序的高可用性和负载均衡
第一步,编写deployment.yaml
# apiVersion: apps/v1
# 定义了 Kubernetes API 的版本。apps/v1 是稳定版本,用于创建和管理 Deployment 资源。
apiVersion: apps/v1# kind: Deployment
# 声明这个 Kubernetes 资源的类型。Deployment 用于管理应用程序的副本集和滚动更新。
kind: Deployment# metadata:
# 包含资源的元数据信息,Kubernetes 通过 metadata 字段中的各类信息来跟踪和管理资源。
metadata:# name: aijava# 定义 Deployment 的名称,这里是 aijava。# 用途:用于唯一标识这个 Deployment,方便在其他地方引用。name: aijava# spec:
# 定义 Deployment 的规格,即 Deployment 的具体配置。
spec:# replicas: 1# Pod 的副本数量,即 Deployment 将运行的相同 Pod 数量。# 用途:确保应用程序具有高可用性和负载均衡。replicas: 1# selector:# 定义如何选择和管理 Pod。selector:# matchLabels:# 定义标签选择器,选择带有 app: aijava 标签的 Pod。# 用途:确保 Deployment 只管理带有指定标签的 Pod。matchLabels:app: aijava# template:# 定义 Pod 的模板,即每个副本 Pod 的配置。template:# metadata:# 包含 Pod 的元数据信息。metadata:# labels:# 定义 Pod 的标签。# 用途:用于标识和选择 Pod。labels:app: aijava# spec:# 定义 Pod 的规格,即 Pod 的具体配置。spec:# containers:# 定义 Pod 中的容器列表。containers:- # name: aijava# 定义容器的名称,这里是 aijava。# 用途:用于唯一标识这个容器,方便在其他地方引用。name: aijava# image: aijava:1.0# 定义容器使用的镜像,Kubernetes 会从容器镜像仓库中拉取它。# 用途:指定容器运行的应用程序版本。image: aijava:1.0# ports:# 定义容器内部监听的应用端口。# 用途:确保 Kubernetes 知道如何将流量路由到容器。ports:- # containerPort: 8999# 指定容器内部监听的应用端口。# 用途:应用程序在容器内监听的端口。containerPort: 8999
第二步,编写service.yaml
# apiVersion: v1
# 指定 Kubernetes API 的版本。v1 是稳定版本,用于创建和管理基本的 Kubernetes 资源。
apiVersion: v1# kind: Service
# 指定资源的类型。在这里,资源类型是 Service,表示这是一个 Kubernetes 服务。
kind: Service# metadata:
# 包含资源的元数据信息。
metadata:# name: aijava# 定义服务的名称,这里是 aijava。# 用途:用于唯一标识这个服务,方便在其他地方引用。name: aijava# spec:
# 定义服务的规格,即服务的具体配置。
spec:# type: NodePort# 定义服务的类型。NodePort 类型的服务会在每个节点上开放一个端口,通过这个端口可以访问服务。# 用途:适用于本地开发环境或不需要外部负载均衡器的场景。type: NodePort# ports:# 定义服务的端口配置。ports:- # port: 9999# 定义服务对外暴露的端口。外部客户端通过这个端口访问服务。# 用途:客户端可以通过 http://<node-ip>:9999 访问服务。port: 9999# targetPort: 8999# 定义集群内部目标 Pod 上运行的容器所监听的端口。# 用途:Kubernetes 会将请求从 port 转发到 targetPort,即从 9999 转发到 8999。targetPort: 8999# selector:# 定义服务选择哪些 Pod。selector:# app: aijava# 定义标签选择器,选择带有 app: aijava 标签的 Pod。# 用途:确保服务将流量路由到正确的 Pod。app: aijava
第三步,构建pod
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
这是他们的流程图:
kubectl apply -f deployment.yaml
└── Kubernetes 创建 Deployment 资源└── 创建 ReplicaSet└── 创建 Pod└── 拉取镜像并启动容器
kubectl apply -f service.yaml
└── Kubernetes 创建 Service 资源└── 选择匹配的 Pod└── 分配节点端口└── 负载均衡流量到后端 Pod
第四步,验证
kubectl get pods
kubectl get services
这里我们看到pod的状态,Kubernetes 已经成功创建了 Deployment 和 Service,并且 Pod 已经处于运行状态。现在可以通过节点的 IP 地址和节点端口来访问我们的应用程序。
服务 aijava
的节点端口是 31431
,所以我们可以通过
http://127.0.0.1:31431/index2.html?message=你好
去访问我们的应用了!!
总结
至此,我们成功的将一个web应用部署到k8s中,当然,k8s还有docker中有很多东西在本文中没有展现出来,富贵同学认为,学东西之前你得学会怎么用,然后才问为什么,先上手,然后再深入,而不是先深入,再上手。
那么在后续的过程中,欢迎关注作者:掉头发的王富贵,一起学习更多k8s的知识