SpringBoot应用:Docker与Kubernetes全栈实战秘籍

什么是docker?

首先,咱们得了解一下什么是docker。它其实就是一种开源的平台,用来自动部署、管理、扩展和运行各种应用程序的技术。它允许开发人员把他们编写的应用程序和相关的依赖库,全都封装到一个叫做"容器"的小家伙里面,然后就能在任何只要装了Docker的环境里跑这个应用!Docker有很多牛逼的功能,例如流畅的跨平台兼容性、严格的资源隔离、精确的版本控制以及超高的资源利用率等等!

为什么要用docker?

接下来,科普一些Docker的背景知识,首先,docker之所以能诞生,主要是因为传统的应用部署方式面临着以下几个难题:

  1. 旧有的应用部署手段:时间回到Docker刚刚面世的那会儿,应用程序的部署往往依赖于特定的操作系统环境配置和依赖项。然而,这种依赖性容易引发各种不统一的问题,无论是开发环境和生产环境之间的差异,还是依赖项之间的冲突,都可能造成应用程序在别的环境下“水土不服”,无法好好运转。
  2. 传统虚拟化技术的短板:以前我们常用的虚拟化技术,比如说VMWare或者VirtualBox,虽然能在一台物理机器上跑出好几个虚拟机,每个虚拟机都有自己独立的操作系统。但是,虚拟机通常比较重,启动和运行都要消耗大量的资源,导致性能损耗严重。
  3. 微服务架构的崛起:近年来,微服务架构逐渐成为了主流,应用程序被拆分成了许多个独立的小服务。而这些小服务则需要在各种各样的环境中快速进行部署和伸缩。如果还是采用传统的部署方式,就很难满足微服务灵活性和扩展性的要求了。

docker有什么?

c577dba308a089274cd99ba773f11054

图片来自csdn@小生凡一

镜像就像是一个应用程序的“模板”。它包含了运行应用所需的一切,比如操作系统、库和应用代码。你可以把它想象成一个打包好的应用快照,随时可以用来创建容器。

容器是镜像的实际运行实例。你可以把它当成是根据模板(镜像)生产出来的一个具体的产品。容器是轻量级的,启动速度快,而且每个容器都有自己独立的运行环境。比如,你可以用同一个镜像启动多个容器,每个容器里跑的应用都是独立的。

仓库是用来存储和共享镜像的地方。你可以把它理解为一个镜像的“库”,在这里你可以找到别人上传的镜像,也可以把自己创建的镜像上传到这里。Docker Hub 是最常用的公共仓库,但你也可以设置自己的私有仓库。

什么是Kubernetes?

Kubernetes(简称 K8s)是一个开源的容器编排系统,用于自动化容器化应用程序的部署、扩展和管理。Kubernetes 提供了一种声明式的配置模型,使得用户可以定义应用程序的期望状态,Kubernetes 会自动确保系统始终处于该状态。

Docker 负责创建和管理容器,Kubernetes 负责管理和调度这些容器,他们的关系犹如这样:

Kubernetes

例如在java微服务的开发过程中,我们要新增这个微服务的节点,如果没用docker,我们是不是要把jar包上传到服务器,然后再把它启动起来,一套流程下来很麻烦,但是你用了docker配合k8s之后,你只需要点一个加号键,就新增了一个节点(pod),点两次就新增了两个,就像这样:

f510fd3944ba4bb6945a90dbda53d124

怎么安装docker

第一步,下载安装包

在Windows中docker有个桌面程序叫做Docker Desktop,这个是在mac和Windows上面独有的程序,在Linux上面是没有的,所以我们点击下载之后,我们会得到一个exe安装包

官网地址https://www.docker.com/

第二步,准备环境

先打开两个设置,控制面板》程序》启用或关闭windows功能》图中倒数第二和第三个点击勾选》重启电脑即可。
ac6c3d951e4f4fa9a7c49830a39e3da7
到本文发布截止时间,官网的exe包是4.33.1的版本,如果你的电脑太老了,这个时候就会出现下面的情况,这个时候我们需要安装4.24.1以下的版本。

877b79065e5b464c861ea23fc9248790
这里我们选的是4.24.0版本的

第三步,安装

一路狂点下一步,页面叫我们注销一下以启用docker,我们按照他的提示执行就行了

第四步,注册账号

上面我们安装完成并且重启之后,进入这个软件,他会让我们注册一个账户,如果我们没有就可以先跳过这个环境以后注册也是可以的

注册这个账号的目的就是可以推送你自己的镜像到docker的中央仓库(和maven的中央仓库差不多),如果后面我们需要注册一个账号的话,点击右上角的sign in浏览器自动就会跳出页面,如果我们需要注册我们就点击sign up,之后会让我们输入邮箱,用户名和密码

第五步,验证是否可以使用

登录/跳过登录之后 这个时候我们看左下角显示的是engine running,意思就是docker以及启动起来了
之后我们用cmd运行docker images命令,出现下面的情况就算成功安装了
a91e0998b70b4177a3fa3f4b3ad5c9f6

第六步,配置国内镜像源

docker的默认仓库也是在国外,所以国内有时候甚至经常pull拉不了镜像,就像github一样,所以我们需要配置国内的镜像库,但是前段时候听说docker镜像很多不能用了,但是博主还是找了一个目前可以用的镜像库,有可能以后要经常换,这个是暂时没有办法的
在docker右上角的设置里面选择docker engine(释义:引擎,发动机;机车,火车头;工具),加入蓝色框框框住的文字,下面已贴出
3941e087b57843799ef24ed5c2c2586a

	,
  "registry-mirrors": [
    "https://docker.m.daocloud.io"
  ]

注意有个逗号,之后右下角的 Apply & restart 就会亮起,我们点击等他重启即可,之后我们cmd 输入docker info就会出现我们刚刚配置的镜像。

怎么安装Kubernetes?

第一步,找到对应的版本

image-20241124142002624

第二步,下载k8s

image-20241124142036752

https://github.com/AliyunContainerService/k8s-for-docker-desktop

选择好对应的版本分支,进行源码下载,解压好之后你会得到这样的一个文件夹

image-20241124142216510

第三步,安装Kubernetes

以管理员身份运行” 的 PowerShell 中执行 Set-ExecutionPolicy RemoteSigned 命令。

在Windows上,使用 PowerShell执行

 .\load_images.ps1

执行完成之后打开docker desktop,选择图中所选的,然后重启docker desktop 等待即可。安装启动完成后,即可在左下角看到docker engine 和 Kubernetes 是蓝色的运行标志

image-20241124142613104

安装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对话,一问一答),最终的效果是前端可以直接访问,并且可以通过页面输出你的问题

msedge_5TLY4MyPQF

第一步,新建一个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

image-20240920102559875

第三步,编写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=你好,介绍一下你自己

msedge_5TLY4MyPQF

这样,我们的web应用就写好了

部署到docker

第一步,编写Dockerfile文件

FROM openjdk:22-slim
COPY spring-ai-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
  1. FROM openjdk:22-slim
    • 这一行指定了基础镜像。openjdk:22-slim 是一个基于 Debian 的轻量级镜像,包含了 OpenJDK 22。slim 版本比标准版本更小,适合生产环境使用。FROM 里面的jdk版本比项目中的大就行了(项目中17)
  2. COPY spring-ai-0.0.1-SNAPSHOT.jar app.jar
    • 这一行将本地文件系统中的 spring-ai-0.0.1-SNAPSHOT.jar 文件复制到 Docker 镜像的根目录下,并重命名为 app.jar
  3. ENTRYPOINT [“java”,“-jar”,“/app.jar”]
    • 这一行设置了容器启动时的默认命令。["java", "-jar", "/app.jar"] 表示使用 Java 运行 /app.jar 文件。ENTRYPOINT 指令确保即使在命令行中传递其他参数,这些参数也会作为参数传递给 java -jar /app.jar

第二步,打包jar包

直接上手就是一个 mvn clean package,然后将打出来的jar放到和Dockerfile 同目录下面,就像这样

image-20241124145328787

第三步,构建镜像

docker build -t aijava:1.0 .    

当你运行上述命令时,Docker 会执行以下步骤:

  1. 读取 Dockerfile:Docker 会读取当前目录下的 Dockerfile。
  2. 构建镜像层
    • 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 文件。
  3. 标记镜像:构建完成后,Docker 会将新镜像标记为 aijava:1.0

image-20241124145715296

第四步,启动镜像

运行一个 Docker 容器:

docker run -d -p 8999:8999 aijava:1.0
  • docker run:这是运行 Docker 容器的命令。
  • -d:表示在后台运行容器。
  • -p 8999:8999:将宿主机的 8999 端口映射到容器的 8999 端口。
  • aijava:1.0:这是要运行的镜像名称和标签。

image-20241124150121201

然后我们就可以在浏览器验证这个容器了,如图就是没问题的!!

部署Kubernetes

部署之前我们先认识一下两个文件:

deployment.yamlservice.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

image-20241124153249155

这里我们看到pod的状态,Kubernetes 已经成功创建了 Deployment 和 Service,并且 Pod 已经处于运行状态。现在可以通过节点的 IP 地址和节点端口来访问我们的应用程序。

服务 aijava 的节点端口是 31431,所以我们可以通过

http://127.0.0.1:31431/index2.html?message=你好

去访问我们的应用了!!

image-20241124153606751

总结

至此,我们成功的将一个web应用部署到k8s中,当然,k8s还有docker中有很多东西在本文中没有展现出来,富贵同学认为,学东西之前你得学会怎么用,然后才问为什么,先上手,然后再深入,而不是先深入,再上手

那么在后续的过程中,欢迎关注作者:掉头发的王富贵,一起学习更多k8s的知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

掉头发的王富贵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值