跳至正文

🚀 部署

📖 什么是部署?

部署是将开发的应用程序在实际生产环境中运行的过程。使用Docker,您可以在任何地方以相同的方式运行它!

💡 JAR构建

build.gradle.kts配置

plugins {
application
kotlin("jvm") version "1.9.0"
id("io.ktor.plugin") version "2.3.5"
}

application {
mainClass.set("com.example.ApplicationKt")
}

ktor {
fatJar {
archiveFileName.set("app.jar")
}
}

构建命令

# JAR 파일 생성
./gradlew buildFatJar

# 실행
java -jar build/libs/app.jar

🎯 环境配置

application.conf

ktor {
deployment {
port = 8080
port = ${?PORT} # 환경 변수 우선
}

application {
modules = [ com.example.ApplicationKt.module ]
}
}

database {
url = "jdbc:postgresql://localhost:5432/mydb"
url = ${?DATABASE_URL}
user = "admin"
user = ${?DATABASE_USER}
password = "password"
password = ${?DATABASE_PASSWORD}
}

使用环境变量

fun Application.module() {
val dbUrl = environment.config.property("database.url").getString()
val dbUser = environment.config.property("database.user").getString()
val dbPassword = environment.config.property("database.password").getString()

Database.connect(
url = dbUrl,
user = dbUser,
password = dbPassword
)

routing {
get("/health") {
call.respondText("OK")
}
}
}

🐳 Docker部署

Dockerfile

# 1단계: 빌드
FROM gradle:8.4-jdk17 AS build
WORKDIR /app

COPY build.gradle.kts settings.gradle.kts ./
COPY src ./src

RUN gradle buildFatJar --no-daemon

# 2단계: 실행
FROM openjdk:17-jdk-slim
WORKDIR /app

COPY --from=build /app/build/libs/app.jar ./app.jar

EXPOSE 8080

CMD ["java", "-jar", "app.jar"]

docker-compose.yml

version: '3.8'

services:
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=jdbc:postgresql://db:5432/mydb
- DATABASE_USER=admin
- DATABASE_PASSWORD=secret
depends_on:
- db

db:
image: postgres:15
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"

volumes:
db-data:

执行

# 빌드 및 실행
docker-compose up -d

# 로그 확인
docker-compose logs -f app

# 중지
docker-compose down

🔧 生产配置

日志记录

import org.slf4j.LoggerFactory

val logger = LoggerFactory.getLogger("Application")

fun Application.module() {
logger.info("애플리케이션 시작")

routing {
get("/") {
logger.info("/ 요청")
call.respondText("Hello!")
}
}
}

CORS配置

import io.ktor.server.plugins.cors.routing.*

fun Application.module() {
install(CORS) {
allowHost("myapp.com", schemes = listOf("https"))
allowHeader(HttpHeaders.ContentType)
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Post)
allowCredentials = true
}
}

压缩

import io.ktor.server.plugins.compression.*

fun Application.module() {
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024)
}
}
}

🛡️ 安全性

HTTPS配置

embeddedServer(Netty, environment = applicationEngineEnvironment {
connector {
port = 8080
}

sslConnector(
keyStore = KeyStore.getInstance("PKCS12").apply {
load(FileInputStream("keystore.p12"), "password".toCharArray())
},
keyAlias = "myalias",
keyStorePassword = { "password".toCharArray() },
privateKeyPassword = { "password".toCharArray() }
) {
port = 8443
}
}).start(wait = true)

速率限制(简单版本)

class RateLimiter {
private val requests = mutableMapOf<String, MutableList<Long>>()
private val limit = 100 // 100 requests
private val windowMs = 60_000L // per minute

fun isAllowed(key: String): Boolean {
val now = System.currentTimeMillis()
val userRequests = requests.getOrPut(key) { mutableListOf() }

// 오래된 요청 제거
userRequests.removeIf { it < now - windowMs }

return if (userRequests.size < limit) {
userRequests.add(now)
true
} else {
false
}
}
}

fun Application.module() {
val rateLimiter = RateLimiter()

routing {
intercept(ApplicationCallPipeline.Call) {
val ip = call.request.origin.remoteHost

if (!rateLimiter.isAllowed(ip)) {
call.respond(HttpStatusCode.TooManyRequests)
finish()
}
}

get("/api/data") {
call.respondText("데이터")
}
}
}

🎯 监控

健康检查

fun Application.module() {
routing {
get("/health") {
val dbHealth = try {
transaction {
exec("SELECT 1") {}
}
"ok"
} catch (e: Exception) {
"error"
}

call.respond(
mapOf(
"status" to "ok",
"database" to dbHealth,
"timestamp" to System.currentTimeMillis()
)
)
}
}
}

指标

class Metrics {
private val requestCount = AtomicInteger(0)
private val errorCount = AtomicInteger(0)

fun incrementRequests() = requestCount.incrementAndGet()
fun incrementErrors() = errorCount.incrementAndGet()

fun getMetrics() = mapOf(
"requests" to requestCount.get(),
"errors" to errorCount.get()
)
}

fun Application.module() {
val metrics = Metrics()

routing {
intercept(ApplicationCallPipeline.Monitoring) {
metrics.incrementRequests()
try {
proceed()
} catch (e: Exception) {
metrics.incrementErrors()
throw e
}
}

get("/metrics") {
call.respond(metrics.getMetrics())
}
}
}

🔥 实际部署示例

完整配置

// Application.kt
fun main(args: Array<String>) {
EngineMain.main(args)
}

fun Application.module() {
// 설정 로드
val config = environment.config

// 로깅
install(CallLogging) {
level = Level.INFO
}

// CORS
install(CORS) {
allowHost(config.property("cors.host").getString())
allowCredentials = true
}

// 압축
install(Compression)

// JSON
install(ContentNegotiation) {
json()
}

// 에러 처리
install(StatusPages) {
exception<Throwable> { call, cause ->
log.error("Unhandled exception", cause)
call.respond(
HttpStatusCode.InternalServerError,
mapOf("error" to "Internal server error")
)
}
}

// 데이터베이스
configureDatabases(config)

// 라우팅
configureRouting()
}

fun Application.configureDatabases(config: ApplicationConfig) {
val url = config.property("database.url").getString()
val user = config.property("database.user").getString()
val password = config.property("database.password").getString()

Database.connect(
url = url,
user = user,
password = password
)
}

🤔 常见问题

问题1. 应该部署到哪里?

答: 推荐使用云服务!

# AWS (Elastic Beanstalk, ECS)
# Google Cloud (Cloud Run)
# Azure (App Service)
# DigitalOcean (App Platform)
# Heroku

# Docker만 있으면 어디든 가능!

问题2. 如何实现零停机部署?

答: 使用蓝绿部署或滚动更新!

# docker-compose.yml (Rolling Update)
version: '3.8'

services:
app:
image: myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s

问题3. 如何管理不同环境的配置?

答: 使用配置文件!

// application-dev.conf
ktor {
deployment {
port = 8080
}
}

// application-prod.conf
ktor {
deployment {
port = 80
}
}

// 실행
java -jar app.jar -config=application-prod.conf

🎬 结语

实现安全高效的部署!

핵심 정리:
✅ JAR 빌드로 실행 파일
✅ Docker로 환경 통일
✅ 환경 변수로 설정 분리
✅ Health Check로 모니터링
✅ 보안 설정 필수

恭喜您!您已经完成了Backend系列和所有Kotlin文档!🎉

现在您可以使用Kotlin进行全栈开发了!

全系列回顾:

  • Basics: Kotlin基础语法
  • Practical: 实践应用(扩展函数、异常、文件、正则表达式、DSL)
  • Coroutines: 异步编程
  • Testing: 编写测试
  • Backend: 服务器开发和部署