文章目录
什么是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安装包
第二步,准备环境
先打开两个设置,控制面板》程序》启用或关闭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: 8999
spring:
application:
name: tongyi-example
cloud:
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;
@Autowired
public TongYiSimpleServiceImpl(ChatModel chatModel, StreamingChatModel streamingChatModel) {
this.chatModel = chatModel;
this.streamingChatModel = streamingChatModel;
}
@Override
public String completion(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatModel.call(prompt).getResult().getOutput().getContent();
}
@Override
public 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());
}
@Override
public 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'; // 后端服务器的完整 URL
const 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的知识