π Deployment
π What is Deployment?β
Deployment is the process of running your developed application in a real production environment. Using Docker, you can run it identically anywhere!
π‘ JAR Buildβ
build.gradle.kts Configurationβ
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")
}
}
Build Commandβ
# JAR νμΌ μμ±
./gradlew buildFatJar
# μ€ν
java -jar build/libs/app.jar
π― Environment Configurationβ
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}
}
Using Environment Variablesβ
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 Deploymentβ
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:
Executionβ
# λΉλ λ° μ€ν
docker-compose up -d
# λ‘κ·Έ νμΈ
docker-compose logs -f app
# μ€μ§
docker-compose down
π§ Production Configurationβ
Loggingβ
import org.slf4j.LoggerFactory
val logger = LoggerFactory.getLogger("Application")
fun Application.module() {
logger.info("μ ν리μΌμ΄μ
μμ")
routing {
get("/") {
logger.info("/ μμ²")
call.respondText("Hello!")
}
}
}
CORS Configurationβ
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
}
}
Compressionβ
import io.ktor.server.plugins.compression.*
fun Application.module() {
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024)
}
}
}
π‘οΈ Securityβ
HTTPS Configurationβ
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)
Rate Limiting (Simple Version)β
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("λ°μ΄ν°")
}
}
}
π― Monitoringβ
Health Checkβ
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()
)
)
}
}
}
Metricsβ
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())
}
}
}
π₯ Real-World Deployment Exampleβ
Complete Configurationβ
// 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
)
}
π€ Frequently Asked Questionsβ
Q1. Where should I deploy?β
A: Cloud services are recommended!
# AWS (Elastic Beanstalk, ECS)
# Google Cloud (Cloud Run)
# Azure (App Service)
# DigitalOcean (App Platform)
# Heroku
# Dockerλ§ μμΌλ©΄ μ΄λλ κ°λ₯!
Q2. What about zero-downtime deployment?β
A: Use Blue-Green deployment or Rolling Update!
# docker-compose.yml (Rolling Update)
version: '3.8'
services:
app:
image: myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
Q3. How do I manage environment-specific configurations?β
A: Use profiles!
// application-dev.conf
ktor {
deployment {
port = 8080
}
}
// application-prod.conf
ktor {
deployment {
port = 80
}
}
// μ€ν
java -jar app.jar -config=application-prod.conf
π¬ Conclusionβ
For safe and efficient deployment!
ν΅μ¬ μ 리:
β
JAR λΉλλ‘ μ€ν νμΌ
β
Dockerλ‘ νκ²½ ν΅μΌ
β
νκ²½ λ³μλ‘ μ€μ λΆλ¦¬
β
Health Checkλ‘ λͺ¨λν°λ§
β
보μ μ€μ νμ
Congratulations! You have completed the Backend series and all Kotlin documentation! π
Now you can do full-stack development with Kotlin!
Review of the entire series:
- Basics: Kotlin fundamentals
- Practical: Practical applications (extension functions, exceptions, files, regex, DSL)
- Coroutines: Asynchronous programming
- Testing: Writing tests
- Backend: Server development and deployment