SpringBoot部署应用到本地k8s

在windows+IDEA环境下,使用gradle将springboot打包,制作成镜像发布到docker上并部署到本地k8s访问web应用。

配置文件

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("plugin.jpa") version "1.6.10"
    id("org.springframework.boot") version "2.6.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.10"
    kotlin("plugin.spring") version "1.6.10"
    kotlin("plugin.allopen") version "1.4.32"
    kotlin("kapt") version "1.4.32"
}

group = "com.xun"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}

dependencies {
    kapt("org.springframework.boot:spring-boot-configuration-processor")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-mustache")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(module = "junit")
        exclude(module = "mockito-core")
    }
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
    testImplementation("com.ninja-squad:springmockk:3.0.1")
}

allOpen{
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.Embeddable")
    annotation("javax.persistence.MappedSuperclass")
}

tasks.withType {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}

tasks.withType {
    useJUnitPlatform()
}

使用gradle打jar包

SpringBoot部署应用到本地k8s_第1张图片
打包后的jar包会放在build->libs目录下
SpringBoot部署应用到本地k8s_第2张图片

编写Dockerfile

FROM java
MAINTAINER [email protected]
VOLUME /tmp
ADD /build/libs/springbootkotlin1-0.0.1-SNAPSHOT.jar myspringbootkotlinapp.jar
ENTRYPOINT ["java","-jar","myspringbootkotlinapp.jar"]

制作image

启动docker,启动minikube

minikube start

在Dockerfile所在目录下运行shell,制作镜像

docker build -t myspringbootkotlinapp .

SpringBoot部署应用到本地k8s_第3张图片

查看镜像

docker images

在这里插入图片描述

上传镜像

打标签

docker tag myspringbootkotlinapp sunzhexun/myspringbootkotlinapp

登陆docker

docker login

在这里插入图片描述

push镜像

 docker push sunzhexun/myspringbootkotlinapp

SpringBoot部署应用到本地k8s_第4张图片
SpringBoot部署应用到本地k8s_第5张图片

minikube创建deployment

自动创建yaml文件

kubectl create deployment myspringbootkotlinapp --image=sunzhexun/myspringbootkotlinapp --dry-run=client -o=yaml > deployment.yaml
echo --- >> deployment.yaml

在这里插入图片描述

kubectl create service clusterip myspringbootkotlinapp --tcp=8080:8080 --dry-run=client -o=yaml >> deployment.yaml

在这里插入图片描述

apply yaml文件

kubectl apply -f deployment.yaml

在这里插入图片描述

查看deployment/svc

kubectl get all

在这里插入图片描述

创建容器可能需要几秒到几分钟,可以另起一个终端,查看dashboard

minikube dashboard

SpringBoot部署应用到本地k8s_第6张图片
创建完成后,
SpringBoot部署应用到本地k8s_第7张图片
暴露服务

kubectl port-forward svc/myspringbootkotlinapp 8080:8080

在这里插入图片描述

访问服务

localhost:8080
此时就可以本地访问我们的springboot应用啦
SpringBoot部署应用到本地k8s_第8张图片

附录

此springboot项目代码(使用的是kotlin+jpa)

目录

SpringBoot部署应用到本地k8s_第9张图片

SpringBoot部署应用到本地k8s_第10张图片

build.gradle.kts配置

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("plugin.jpa") version "1.6.10"
    id("org.springframework.boot") version "2.6.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.10"
    kotlin("plugin.spring") version "1.6.10"
    kotlin("plugin.allopen") version "1.4.32"
    kotlin("kapt") version "1.4.32"
}


group = "com.xun"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}

dependencies {
    kapt("org.springframework.boot:spring-boot-configuration-processor")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-mustache")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(module = "junit")
        exclude(module = "mockito-core")
    }
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
    testImplementation("com.ninja-squad:springmockk:3.0.1")
}

allOpen{
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.Embeddable")
    annotation("javax.persistence.MappedSuperclass")
}


tasks.withType {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}

tasks.withType {
    useJUnitPlatform()
}

Entities.kt

实体类

import java.time.LocalDateTime
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.ManyToOne

@Entity
class Article(
    var title: String,
    var headline: String,
    var content: String,
    @ManyToOne var author: User,
    var slug: String = title.toSlug(),
    var addedAt: LocalDateTime = LocalDateTime.now(),
    @Id @GeneratedValue var id: Long? = null)

@Entity
class User(
    var login: String,
    var firstname: String,
    var lastname: String,
    var description: String? = null,
    @Id @GeneratedValue var id: Long? = null)

@Entity
class Product(
    var productName: String,
    var categoryId: Int,
    @Id @GeneratedValue var id: Long? = null)

@Entity
class Category(
    var categoryName:String,
    @Id @GeneratedValue var id: Long? = null)

Reposotiry

持久层

import com.xun.springbootkotlin1.Article
import org.springframework.data.repository.CrudRepository
interface ArticleRepository: CrudRepository<Article, Long> {
    fun findBySlug(slug: String): Article?
    fun findAllByOrderByAddedAtDesc(): Iterable<Article>
}
import com.xun.springbootkotlin1.Category
import org.springframework.data.repository.CrudRepository
interface CategoryRepository:CrudRepository<Category,Long> {
}
import com.xun.springbootkotlin1.Product
import org.springframework.data.repository.CrudRepository
interface ProductRepository:CrudRepository<Product,Long> {
}
import com.xun.springbootkotlin1.User
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, Long> {
    fun findByLogin(login: String): User?
}

BlogConfiguration

初始化Entity数据

import com.xun.springbootkotlin1.repository.ArticleRepository
import com.xun.springbootkotlin1.repository.CategoryRepository
import com.xun.springbootkotlin1.repository.ProductRepository
import com.xun.springbootkotlin1.repository.UserRepository
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class BlogConfiguration {
    @Bean
    fun databaseInitializer(userRepository: UserRepository,
                            articleRepository: ArticleRepository,
                            productRepository: ProductRepository,
                            categoryRepository: CategoryRepository
    ) = ApplicationRunner {

        val authorInfo = userRepository.save(User("author", "喆勋", "孙"))
        articleRepository.save(Article(
            title = "标题2",// 必须是数字/英文结尾?
            headline = "头条新闻",
            content = "上海昨天新增24例本土新冠肺炎确诊患者,新增734例本土无症状感染者",
            author = authorInfo
        ))
        articleRepository.save(Article(
            title = "标题1",
            headline = "头条新闻",
            content = "山西吕梁商代墓出土特殊形制青铜短剑",
            author = authorInfo
        ))
        productRepository.save(Product(
            productName = "iphone13",
            categoryId = 1
        ))
        productRepository.save(Product(
            productName = "HUAWEI mate 40 pro",
            categoryId = 1
        ))
        productRepository.save(Product(
            productName = "西湖醋鱼",
            categoryId = 2
        ))
        productRepository.save(Product(
            productName = "北京烤鸭",
            categoryId = 2
        ))
        productRepository.save(Product(
            productName = "四川麻婆豆腐",
            categoryId = 2
        ))
        productRepository.save(Product(
            productName = "NIKE",
            categoryId = 3
        ))
        categoryRepository.save(Category(
            categoryName = "数码产品",
            id = 1
        ))
        categoryRepository.save(Category(
            categoryName = "食品",
            id = 2
        ))
        categoryRepository.save(Category(
            categoryName = "运动服装",
            id = 3
        ))
    }
}

BlogProperties

@ConstructorBinding
@ConfigurationProperties("blog")
data class BlogProperties(var title: String, val banner: Banner) {
   data class Banner(val title: String? = null, val content: String)
}

Controller

HtmlController

@Controller
class HtmlController(private val repository: ArticleRepository, private val properties: BlogProperties) {

    @GetMapping("/")
    fun blog(model: Model): String {
        model["title"]=properties.title
        model["banner"] = properties.banner
        model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
        return "blog"
    }

    @GetMapping("/article/{slug}")
    fun article(@PathVariable slug: String, model: Model): String {
        val article = repository
            .findBySlug(slug)
            ?.render()
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")
        model["title"] = article.title
        model["article"] = article
        return "article"
    }

    fun Article.render() = RenderedArticle(
        slug, title, headline, content, author, addedAt.format()
    )

    data class RenderedArticle(
        val slug: String,
        val title: String,
        val headline: String,
        val content: String,
        val author: User,
        val addedAt: String
    )
}

HttpController.kt

@RestController
@RequestMapping("/api/article")
class ArticleController(private val repository: ArticleRepository){
    @GetMapping("/")
    fun findAll() = repository.findAllByOrderByAddedAtDesc()
    @GetMapping("/{slug}")
    fun findOne(@PathVariable slug:String)=repository.findBySlug(slug)?:
    throw ResponseStatusException(HttpStatus.NOT_FOUND,"This article does not exist")
}

@RestController
@RequestMapping("/api/user")
class UserController(private val repository: UserRepository){
    @GetMapping("/")
    fun findAll() = repository.findAll()
    @GetMapping("/{login}")
    fun findOne(@PathVariable login:String)=repository.findByLogin(login)?:
    throw ResponseStatusException(HttpStatus.NOT_FOUND,"This user does not exist")
}

@RestController
@RequestMapping("/api/product")
class ProductController(private val repository: ProductRepository){
    @GetMapping("/")
    fun findAll() = repository.findAll()
}

@RestController
@RequestMapping("/api/category")
class  CategoryController(private val repository:CategoryRepository){
    @GetMapping("/")
    fun findAll() = repository.findAll()
}

kotlin扩展函数

Extensions.kt

import java.time.LocalDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
import java.util.*

fun LocalDateTime.format() = this.format(englishDateFormatter)

private val daysLookup = (1..31).associate { it.toLong() to getOrdinal(it) }

private val englishDateFormatter = DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd")
    .appendLiteral(" ")
    .appendText(ChronoField.DAY_OF_MONTH, daysLookup)
    .appendLiteral(" ")
    .appendPattern("yyyy")
    .toFormatter(Locale.ENGLISH)

private fun getOrdinal(n: Int) = when {
    n in 11..13 -> "${n}th"
    n % 10 == 1 -> "${n}st"
    n % 10 == 2 -> "${n}nd"
    n % 10 == 3 -> "${n}rd"
    else -> "${n}th"
}

fun String.toSlug() = toLowerCase()
    .replace("\n", " ")
    .replace("[^a-z\\d\\s]".toRegex(), " ")
    .split(" ")
    .joinToString("-")
    .replace("-+".toRegex(), "-")

启动类

@SpringBootApplication
@EnableConfigurationProperties(BlogProperties::class)
class Springbootkotlin1Application

fun main(args: Array<String>) {
    runApplication<Springbootkotlin1Application>(*args){
        setBannerMode(Banner.Mode.OFF)
    }
}

模板mustache

blog.mustache

{{> header}}

<h1>{{title}}h1>

<div class="articles">
    {{#banner.title}}
        <section>
            <header class="banner">
                <h2 class="banner-title">{{banner.title}}h2>
            header>
            <div class="banner-content">
                {{banner.content}}
            div>
        section>
    {{/banner.title}}
    {{#articles}}
        <section>
            <header class="article-header">
                <h2 class="article-title">
                    <a href="/article/{{slug}}">{{title}}a>
                h2>
                <div class="article-meta">By
                    <strong>{{author.firstname}}strong>, on <strong>{{addedAt}}strong>
                div>
            header>
            <div class="article-description">
                {{headline}}
            div>
        section>
    {{/articles}}
div>
<br><br><br>
<div class="title"><strong>网上商城strong>div>
<div class="hyperlink">
    <a href="/api/product/">商品列表a>
    <a href="/api/category/">类型列表a>
div>

{{> footer}}

header.mustache

<html>
<head>
    <title>{{title}}title>
head>
<body>

article.mustache

{{> header}}

<section class="article">
    <header class="article-header">
        <h1 class="article-title">{{article.title}}h1>
        <p class="article-meta">By
            <strong>{{article.author.firstname}}strong>, on
            <strong>{{article.addedAt}}strong>
        p>
    header>

    <div class="article-description">
        {{article.headline}}
        {{article.content}}
    div>
section>

{{> footer}}

footer.mustache

body>
html>

application.properties

blog.title=Blog
blog.banner.title=Warning
blog.banner.content=The blog will be down tomorrow.

setting.gradle.kts

rootProject.name = "springbootkotlin1"

项目参考文档

Building web applications with Spring Boot and Kotlin
Spring Boot Kubernetes

你可能感兴趣的:(springboot,minikube,kotlin,docker,spring,boot,kubernetes,kotlin)