Infrastructure as Code (IaC)
Infrastructure as Code (IaC), modern yazılım geliştirme süreçlerinin temel taşlarından biri haline gelmiştir. Bu yaklaşım, altyapı kaynaklarının kod aracılığıyla tanımlanması, dağıtılması ve yönetilmesi prensibiyle çalışır. Geleneksel manuel altyapı yönetiminin yerini alan IaC, tekrarlanabilir, güvenilir ve ölçeklenebilir altyapı yönetimi sunar.
IaC'nin temel felsefi, "altyapı da kod gibi versiyonlanmalı ve yönetilmelidir" düşüncesidir. Bu sayede altyapı değişiklikleri, uygulama kodu değişiklikleri gibi test edilebilir, gözden geçirilebilir ve güvenli bir şekilde dağıtılabilir. DevOps kültürünün ayrılmaz bir parçası olan IaC, geliştirme ve operasyon ekipleri arasındaki işbirliğini güçlendirir.
IaC'nin Temel Prensipleri ve Avantajları
Infrastructure as Code yaklaşımı, altyapı yönetiminde köklü değişiklikler getiren bir paradigmadır. Bu yaklaşımın benimsenmesi, organizasyonlara sayısız avantaj sağlar ve modern yazılım geliştirme süreçlerinin vazgeçilmez bir parçası haline gelir.
Tutarlılık ve Standardizasyon
IaC'nin en önemli avantajlarından biri, tüm ortamlarda tutarlı altyapı konfigürasyonu sağlamasıdır. Geliştirme, test, staging ve production ortamları arasındaki farklılıklar minimize edilir. Bu durum, "benim makinemde çalışıyor" (works on my machine) problemini büyük ölçüde ortadan kaldırır.
Environment Parity: Development ortamında test edilen altyapı konfigürasyonu, production ortamında da aynı şekilde çalışır. Bu sayede deployment sürecinde yaşanan sürprizler azalır ve uygulamaların farklı ortamlar arasında sorunsuz çalışması sağlanır.
Configuration Drift Prevention: Altyapı bileşenlerinin zaman içinde beklenmeyen değişikliklere uğraması (configuration drift) sorunu, IaC ile önlenir. Kod repository'sindeki tanımlar, altyapının gerçek durumunun tek source of truth'u haline gelir.
Hız ve Operasyonel Verimlilik
Manuel altyapı kurulumları saatler hatta günler sürebilirken, IaC ile aynı işlemler dakikalar içinde tamamlanabilir. Bu hız artışı, özellikle mikroservis mimarilerinde kritik önem taşır.
Paralel Kaynak Oluşturma: Terraform gibi araçlar, birbirine bağımlı olmayan kaynakları paralel olarak oluşturabilir. Bu özellik, büyük ve karmaşık altyapıların kurulum süresini önemli ölçüde kısaltır.
Otomatik Skalasyon: Auto Scaling Group'lar, Load Balancer'lar ve diğer dinamik bileşenler IaC ile tanımlandığında, sistemin ihtiyaca göre otomatik olarak büyümesi veya küçülmesi sağlanır.
Güvenlik ve Compliance
IaC, güvenlik politikalarının kod seviyesinde tanımlanmasını ve uygulanmasını mümkün kılar. Security-as-Code yaklaşımı ile güvenlik kontrolleri, altyapının ayrılmaz bir parçası haline gelir.
Policy-as-Code: Open Policy Agent (OPA) gibi araçlarla güvenlik politikaları kod olarak tanımlanır ve IaC şablonlarına otomatik olarak uygulanır.
Audit Trail: Tüm altyapı değişiklikleri git history'sinde takip edilir, kim ne zaman hangi değişikliği yaptığı kolayca izlenebilir.
Terraform ile Kapsamlı Altyapı Yönetimi
Terraform, HashiCorp tarafından geliştirilen ve çoklu cloud provider desteği sunan bir Infrastructure as Code aracıdır. Declarative (bildirimsel) yaklaşımı benimseyen Terraform, istenilen altyapı durumunu tanımlar ve mevcut durumla karşılaştırarak gerekli değişiklikleri otomatik olarak uygular.
Terraform'un Temel Avantajları
Multi-Cloud Destekleme: AWS, Azure, Google Cloud Platform, Kubernetes ve 1000'den fazla provider ile çalışabilir. Bu esneklik, vendor lock-in'den kaçınmayı ve hibrit bulut stratejileri geliştirmeyi mümkün kılar.
State Management: Terraform state dosyası, altyapının mevcut durumunu tutar. Bu sayede hangi kaynakların zaten mevcut olduğu, hangilerinin oluşturulması gerektiği ve hangilerinin silinmesi gerektiği belirlenebilir.
Plan ve Apply Cycle: terraform plan
komutu ile yapılacak değişiklikler önceden görülebilir ve doğrulanabilir. Bu yaklaşım, beklenmeyen değişiklikleri önler ve güvenli deployment sağlar.
Spring Boot Uygulaması için AWS Altyapısı
Spring Boot uygulamalarının production ortamında çalışması için gerekli AWS altyapısı, Terraform ile sistematik olarak tanımlanabilir. Bu yaklaşım, hem güvenilirlik hem de ölçeklenebilirlik açısından kritik önem taşır.
Network Altyapısı Tasarımı
Modern cloud mimarisinin temelini oluşturan VPC (Virtual Private Cloud) yapısı, güvenlik ve performans açısından dikkatlice tasarlanmalıdır. Çok katmanlı mimari (multi-tier architecture) yaklaşımıyla public ve private subnet'ler ayrıştırılarak, güvenlik zonlaması oluşturulur.
Public Subnet'ler: Internet erişimi olan ve Load Balancer, NAT Gateway gibi bileşenlerin yerleştirildiği alanlar. Bu subnet'lerde çalışan kaynaklar, Internet Gateway üzerinden doğrudan internet erişimine sahiptir.
Private Subnet'ler: Uygulama sunucuları ve veritabanlarının çalıştığı, doğrudan internet erişimi olmayan güvenli alanlar. Bu subnet'lerdeki kaynaklar, NAT Gateway veya NAT Instance üzerinden kontrollü internet erişimi sağlar.
Güvenlik Grupları ve Network ACL'ler
AWS Security Group'lar, sanal güvenlik duvarları görevi görür ve trafik kontrolü sağlar. Spring Boot uygulamaları için tipik güvenlik grup konfigürasyonu şunları içerir:
Application Security Group: Spring Boot uygulamasının dinlediği port (genellikle 8080) için Load Balancer'dan gelen trafiği kabul eder. SSH erişimi sadece belirli IP aralıklarından veya bastion host'lardan izin verilir.
Database Security Group: Sadece uygulama sunucularından gelen veritabanı bağlantılarını kabul eder. Bu yaklaşım, veritabanına doğrudan erişimi engeller ve additional security layer sağlar.
Auto Scaling ve High Availability
Production ortamında çalışan Spring Boot uygulamaları, değişken yük durumlarına adaptasyon gösterebilmeli ve yüksek erişilebilirlik sağlamalıdır.
Auto Scaling Group: CPU kullanımı, memory consumption veya custom metric'lere dayalı olarak instance sayısını otomatik olarak artırıp azaltır.
Multi-AZ Deployment: Uygulamanın birden fazla Availability Zone'da çalışması, doğal afetler veya AWS altyapı sorunlarına karşı koruma sağlar.
# variables.tf
variable "app_name" {
description = "Spring Boot application name"
type = string
default = "spring-boot-app"
}
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
default = "dev"
}
variable "instance_count" {
description = "Number of EC2 instances"
type = number
default = 2
}
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "spring-boot-app/terraform.tfstate"
region = "us-west-2"
}
}
provider "aws" {
region = "us-west-2"
}
# VPC ve Networking
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.app_name}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-public-subnet-${count.index + 1}"
Type = "Public"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.app_name}-private-subnet-${count.index + 1}"
Type = "Private"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-igw"
}
}
# Route Tables
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.app_name}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Security Groups
resource "aws_security_group" "app" {
name_prefix = "${var.app_name}-app-"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.app_name}-app-sg"
}
}
# Load Balancer
resource "aws_lb" "main" {
name = "${var.app_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
enable_deletion_protection = false
tags = {
Environment = var.environment
}
}
resource "aws_security_group" "alb" {
name_prefix = "${var.app_name}-alb-"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Target Group
resource "aws_lb_target_group" "app" {
name = "${var.app_name}-tg"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/actuator/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}
tags = {
Name = "${var.app_name}-tg"
}
}
# ALB Listener
resource "aws_lb_listener" "app" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# RDS Database
resource "aws_db_subnet_group" "main" {
name = "${var.app_name}-db-subnet-group"
subnet_ids = aws_subnet.private[*].id
tags = {
Name = "${var.app_name}-db-subnet-group"
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.app_name}-rds-"
vpc_id = aws_vpc.main.id
ingress {
description = "PostgreSQL"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = {
Name = "${var.app_name}-rds-sg"
}
}
resource "aws_db_instance" "main" {
identifier = "${var.app_name}-db"
allocated_storage = 20
max_allocated_storage = 100
storage_type = "gp2"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"
db_name = "appdb"
username = "dbuser"
password = random_password.db_password.result
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = 7
backup_window = "07:00-09:00"
maintenance_window = "sun:09:00-sun:11:00"
skip_final_snapshot = true
deletion_protection = false
tags = {
Name = "${var.app_name}-database"
}
}
resource "random_password" "db_password" {
length = 16
special = true
}
# Launch Template
resource "aws_launch_template" "app" {
name_prefix = "${var.app_name}-"
image_id = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
db_host = aws_db_instance.main.endpoint
db_name = aws_db_instance.main.db_name
db_username = aws_db_instance.main.username
db_password = random_password.db_password.result
app_name = var.app_name
}))
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.app_name}-instance"
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "app" {
name = "${var.app_name}-asg"
vpc_zone_identifier = aws_subnet.public[*].id
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
min_size = 1
max_size = 5
desired_capacity = var.instance_count
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.app_name}-asg-instance"
propagate_at_launch = true
}
}
# Data Sources
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
User Data Script
#!/bin/bash
# user_data.sh
yum update -y
yum install -y java-17-amazon-corretto docker
# Start Docker
systemctl start docker
systemctl enable docker
# Create application user
useradd -m -s /bin/bash springboot
# Create application directories
mkdir -p /opt/springboot
chown springboot:springboot /opt/springboot
# Download application JAR (from S3 or build server)
cd /opt/springboot
wget https://your-artifacts-bucket.s3.amazonaws.com/${app_name}/latest/${app_name}.jar
# Create application configuration
cat > application.yml << EOF
spring:
datasource:
url: jdbc:postgresql://${db_host}:5432/${db_name}
username: ${db_username}
password: ${db_password}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
logging:
level:
com.yourcompany: INFO
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
EOF
# Create systemd service
cat > /etc/systemd/system/${app_name}.service << EOF
[Unit]
Description=${app_name} Spring Boot Application
After=network.target
[Service]
Type=simple
User=springboot
ExecStart=/usr/bin/java -jar /opt/springboot/${app_name}.jar --spring.config.location=/opt/springboot/application.yml
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=${app_name}
[Install]
WantedBy=multi-user.target
EOF
# Set permissions
chown springboot:springboot /opt/springboot/*
chmod +x /opt/springboot/${app_name}.jar
# Start service
systemctl daemon-reload
systemctl enable ${app_name}
systemctl start ${app_name}
# Install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
# Configure CloudWatch agent
cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << EOF
{
"metrics": {
"namespace": "SpringBoot/${app_name}",
"metrics_collected": {
"cpu": {
"measurement": ["cpu_usage_idle", "cpu_usage_iowait", "cpu_usage_user", "cpu_usage_system"],
"metrics_collection_interval": 60
},
"mem": {
"measurement": ["mem_used_percent"],
"metrics_collection_interval": 60
}
}
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/messages",
"log_group_name": "/aws/ec2/${app_name}/system",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
EOF
systemctl enable amazon-cloudwatch-agent
systemctl start amazon-cloudwatch-agent
Outputs
# outputs.tf
output "load_balancer_dns" {
description = "DNS name of the load balancer"
value = aws_lb.main.dns_name
}
output "database_endpoint" {
description = "RDS instance endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
output "database_password" {
description = "Database password"
value = random_password.db_password.result
sensitive = true
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = aws_subnet.private[*].id
}
AWS CloudFormation ile Native IaC
AWS CloudFormation, Amazon Web Services'in kendi geliştirdiği native Infrastructure as Code çözümüdür. JSON veya YAML formatında yazılan template'ler kullanarak AWS kaynaklarını tanımlar ve yönetir. CloudFormation'ın en büyük avantajı, AWS ekosistemi ile derin entegrasyonu ve yeni AWS servislerine olan hızlı desteğidir.
CloudFormation'ın Temel Avantajları
AWS Native Integration: AWS'nin yeni çıkan servisleri, genellikle ilk olarak CloudFormation'da desteklenir. Bu durum, cutting-edge AWS özelliklerini hızlıca kullanmaya başlamak için önemlidir.
Stack Management: CloudFormation, kaynakları stack'ler halinde gruplar. Bu yaklaşım, ilgili kaynakların bir arada yönetilmesini ve toplu olarak silinmesini sağlar.
Drift Detection: CloudFormation, template ile gerçek altyapı durumu arasındaki farkları tespit edebilir ve bu farkları düzeltme önerilerinde bulunur.
Change Sets: Değişikliklerin uygulanmadan önce preview edilmesini sağlar. Bu özellik, risk yönetimi açısından kritik öneme sahiptir.
Spring Boot için CloudFormation Template
Spring Boot uygulamalarının AWS'de deploy edilmesi için gerekli tüm bileşenlerin CloudFormation ile tanımlanması, tutarlı ve tekrarlanabilir deployment'lar sağlar.
# spring-boot-infrastructure.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Production-ready Spring Boot Application Infrastructure'
Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Description: 'Deployment environment'
InstanceType:
Type: String
Default: t3.medium
AllowedValues: [t3.small, t3.medium, t3.large, t3.xlarge]
Description: 'EC2 instance type for Spring Boot application'
MinInstances:
Type: Number
Default: 2
MinValue: 1
MaxValue: 10
Description: 'Minimum number of instances in ASG'
MaxInstances:
Type: Number
Default: 10
MinValue: 1
MaxValue: 20
Description: 'Maximum number of instances in ASG'
ArtifactS3Bucket:
Type: String
Description: 'S3 bucket containing Spring Boot JAR file'
ArtifactS3Key:
Type: String
Description: 'S3 key for Spring Boot JAR file'
Resources:
# VPC ve Network Components
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub '${Environment}-spring-boot-vpc'
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${Environment}-spring-boot-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-public-subnet-1'
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-public-subnet-2'
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.10.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: !Sub '${Environment}-private-subnet-1'
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.11.0/24
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: !Sub '${Environment}-private-subnet-2'
# Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${Environment}-public-rt'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
# Security Groups
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: 'Security group for Application Load Balancer'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: 'HTTP access'
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: 'HTTPS access'
Tags:
- Key: Name
Value: !Sub '${Environment}-alb-sg'
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: 'Security group for Spring Boot application'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: 'HTTP from ALB'
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 10.0.0.0/16
Description: 'SSH from VPC'
Tags:
- Key: Name
Value: !Sub '${Environment}-app-sg'
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub '${Environment}-spring-boot-alb'
Type: application
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub '${Environment}-spring-boot-alb'
# Target Group
SpringBootTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${Environment}-spring-boot-tg'
Port: 8080
Protocol: HTTP
VpcId: !Ref VPC
HealthCheckPath: /actuator/health
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '300'
- Key: stickiness.enabled
Value: 'false'
# ALB Listener
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref SpringBootTargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
# IAM Role for EC2 instances
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: S3ArtifactAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource: !Sub '${ArtifactS3Bucket}/${ArtifactS3Key}'
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2Role
# Launch Template
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub '${Environment}-spring-boot-lt'
LaunchTemplateData:
ImageId: ami-0c02fb55956c7d316 # Amazon Linux 2 AMI
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref ApplicationSecurityGroup
IamInstanceProfile:
Arn: !GetAtt InstanceProfile.Arn
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: !Sub '${Environment}-spring-boot-instance'
- Key: Environment
Value: !Ref Environment
UserData:
Fn::Base64: !Sub |
#!/bin/bash
# System Updates
yum update -y
yum install -y java-17-amazon-corretto wget
# Create application user
useradd -m -s /bin/bash springboot
# Create application directories
mkdir -p /opt/springboot/logs
chown -R springboot:springboot /opt/springboot
# Download Spring Boot application from S3
aws s3 cp s3://${ArtifactS3Bucket}/${ArtifactS3Key} /opt/springboot/application.jar
chown springboot:springboot /opt/springboot/application.jar
chmod +x /opt/springboot/application.jar
# Create application configuration
cat > /opt/springboot/application.yml << 'EOL'
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
health:
show-details: always
endpoint:
health:
probes:
enabled: true
logging:
level:
com.mycompany: INFO
root: WARN
file:
name: /opt/springboot/logs/application.log
pattern:
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
spring:
profiles:
active: ${Environment}
EOL
# Create systemd service
cat > /etc/systemd/system/springboot.service << 'EOL'
[Unit]
Description=Spring Boot Application
After=network.target
[Service]
Type=simple
User=springboot
Group=springboot
WorkingDirectory=/opt/springboot
ExecStart=/usr/bin/java -Xmx1g -Xms512m -jar /opt/springboot/application.jar --spring.config.location=/opt/springboot/application.yml
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=springboot
[Install]
WantedBy=multi-user.target
EOL
# Install and configure CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
# CloudWatch agent configuration
cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << 'EOL'
{
"metrics": {
"namespace": "SpringBoot/${Environment}",
"metrics_collected": {
"cpu": {
"measurement": ["cpu_usage_idle", "cpu_usage_iowait", "cpu_usage_user", "cpu_usage_system"],
"metrics_collection_interval": 60
},
"mem": {
"measurement": ["mem_used_percent"],
"metrics_collection_interval": 60
},
"disk": {
"measurement": ["used_percent"],
"metrics_collection_interval": 60,
"resources": ["*"]
}
}
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/opt/springboot/logs/application.log",
"log_group_name": "/aws/ec2/springboot/${Environment}",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
EOL
# Start services
systemctl daemon-reload
systemctl enable springboot
systemctl start springboot
systemctl enable amazon-cloudwatch-agent
systemctl start amazon-cloudwatch-agent
# Wait for application to be ready
sleep 30
curl -f http://localhost:8080/actuator/health || echo "Application health check failed"
# Auto Scaling Group
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: !Sub '${Environment}-spring-boot-asg'
VPCZoneIdentifier:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: !Ref MinInstances
MaxSize: !Ref MaxInstances
DesiredCapacity: !Ref MinInstances
TargetGroupARNs:
- !Ref SpringBootTargetGroup
HealthCheckType: ELB
HealthCheckGracePeriod: 300
Tags:
- Key: Name
Value: !Sub '${Environment}-spring-boot-asg-instance'
PropagateAtLaunch: true
- Key: Environment
Value: !Ref Environment
PropagateAtLaunch: true
# Auto Scaling Policies
ScaleUpPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref AutoScalingGroup
Cooldown: 300
ScalingAdjustment: 1
PolicyType: SimpleScaling
ScaleDownPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref AutoScalingGroup
Cooldown: 300
ScalingAdjustment: -1
PolicyType: SimpleScaling
# CloudWatch Alarms
CPUAlarmHigh:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: 'Scale up on high CPU'
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 70
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref ScaleUpPolicy
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoScalingGroup
CPUAlarmLow:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: 'Scale down on low CPU'
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 25
ComparisonOperator: LessThanThreshold
AlarmActions:
- !Ref ScaleDownPolicy
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoScalingGroup
Outputs:
LoadBalancerDNS:
Description: 'DNS name of the Application Load Balancer'
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub '${Environment}-spring-boot-alb-dns'
LoadBalancerURL:
Description: 'URL of the Application Load Balancer'
Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
Export:
Name: !Sub '${Environment}-spring-boot-alb-url'
AutoScalingGroupName:
Description: 'Name of the Auto Scaling Group'
Value: !Ref AutoScalingGroup
Export:
Name: !Sub '${Environment}-spring-boot-asg-name'
VPCId:
Description: 'VPC ID'
Value: !Ref VPC
Export:
Name: !Sub '${Environment}-vpc-id'
Nested Stacks ile Modüler Yaklaşım
Büyük ve karmaşık altyapılar için CloudFormation nested stack'leri kullanarak modüler bir yaklaşım benimsenebilir. Bu yöntem, kodu organize etmeyi ve farklı bileşenleri bağımsız olarak yönetmeyi kolaylaştırır.
# master-stack.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Master stack for Spring Boot application'
Parameters:
Environment:
Type: String
Default: dev
Resources:
NetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://my-templates-bucket.s3.amazonaws.com/network-stack.yaml
Parameters:
Environment: !Ref Environment
DatabaseStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://my-templates-bucket.s3.amazonaws.com/database-stack.yaml
Parameters:
Environment: !Ref Environment
VPCId: !GetAtt NetworkStack.Outputs.VPCId
PrivateSubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnetIds
ApplicationStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://my-templates-bucket.s3.amazonaws.com/application-stack.yaml
Parameters:
Environment: !Ref Environment
VPCId: !GetAtt NetworkStack.Outputs.VPCId
PublicSubnetIds: !GetAtt NetworkStack.Outputs.PublicSubnetIds
PrivateSubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnetIds
DatabaseEndpoint: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint
Ansible ile Configuration Management
Ansible, agentless configuration management aracıdır ve IaC'nin configuration kısmını çok iyi yönetir.
Spring Boot Deployment Playbook
# playbook.yml
---
- name: Deploy Spring Boot Application
hosts: app_servers
become: yes
vars:
app_name: "spring-boot-app"
app_version: "{{ lookup('env', 'APP_VERSION') | default('latest') }}"
app_port: 8080
java_version: "17"
tasks:
- name: Update system packages
yum:
name: "*"
state: latest
when: ansible_os_family == "RedHat"
- name: Install Java {{ java_version }}
yum:
name: "java-{{ java_version }}-amazon-corretto"
state: present
- name: Create application user
user:
name: "{{ app_name }}"
system: yes
shell: /bin/bash
home: "/opt/{{ app_name }}"
create_home: yes
- name: Create application directories
file:
path: "{{ item }}"
state: directory
owner: "{{ app_name }}"
group: "{{ app_name }}"
mode: '0755'
loop:
- "/opt/{{ app_name }}"
- "/opt/{{ app_name }}/logs"
- "/opt/{{ app_name }}/config"
- "/opt/{{ app_name }}/backup"
- name: Download application JAR
get_url:
url: "https://your-artifacts.s3.amazonaws.com/{{ app_name }}/{{ app_version }}/{{ app_name }}.jar"
dest: "/opt/{{ app_name }}/{{ app_name }}.jar"
owner: "{{ app_name }}"
group: "{{ app_name }}"
mode: '0755'
notify: restart application
- name: Template application configuration
template:
src: application.yml.j2
dest: "/opt/{{ app_name }}/config/application.yml"
owner: "{{ app_name }}"
group: "{{ app_name }}"
mode: '0644'
notify: restart application
- name: Template systemd service
template:
src: spring-boot.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
mode: '0644'
notify:
- reload systemd
- restart application
- name: Start and enable application service
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
daemon_reload: yes
- name: Wait for application to be ready
uri:
url: "http://localhost:{{ app_port }}/actuator/health"
method: GET
return_content: yes
register: health_check
until: health_check.status == 200
retries: 30
delay: 10
- name: Configure log rotation
template:
src: logrotate.j2
dest: "/etc/logrotate.d/{{ app_name }}"
mode: '0644'
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart application
systemd:
name: "{{ app_name }}"
state: restarted
Jinja2 Templates
# templates/application.yml.j2
spring:
datasource:
url: jdbc:postgresql://{{ database_host }}:5432/{{ database_name }}
username: {{ database_username }}
password: {{ database_password }}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
redis:
host: {{ redis_host }}
port: 6379
password: {{ redis_password }}
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-idle: 8
min-idle: 2
server:
port: {{ app_port }}
tomcat:
max-threads: 200
min-spare-threads: 10
connection-timeout: 20000
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
logging:
level:
com.yourcompany: {{ log_level | default('INFO') }}
org.springframework.security: DEBUG
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: "/opt/{{ app_name }}/logs/application.log"
app:
security:
jwt:
secret: {{ jwt_secret }}
expiration: 86400000
cache:
redis:
enabled: true
ttl: 3600
# templates/spring-boot.service.j2
[Unit]
Description={{ app_name }} Spring Boot Application
After=network.target postgresql.service redis.service
[Service]
Type=simple
User={{ app_name }}
Group={{ app_name }}
ExecStart=/usr/bin/java \
-Xms512m \
-Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-Dspring.profiles.active={{ spring_profiles_active | default('production') }} \
-Dspring.config.location=/opt/{{ app_name }}/config/application.yml \
-jar /opt/{{ app_name }}/{{ app_name }}.jar
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier={{ app_name }}
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/{{ app_name }}/logs
# Resource limits
LimitNOFILE=65536
LimitMEMLOCK=infinity
[Install]
WantedBy=multi-user.target
Kubernetes ile Container Orchestration
Modern uygulamalar için Kubernetes tabanlı deployment daha yaygın hale gelmiştir.
Helm Chart Structure
# Chart.yaml
apiVersion: v2
name: spring-boot-app
description: A Helm chart for Spring Boot application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: 12.1.9
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: 17.3.7
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# values.yaml
# Default values for spring-boot-app
replicaCount: 3
image:
repository: your-registry/spring-boot-app
pullPolicy: IfNotPresent
tag: "latest"
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContext:
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: api.yourcompany.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-tls
hosts:
- api.yourcompany.com
resources:
limits:
cpu: 1000m
memory: 1024Mi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- spring-boot-app
topologyKey: kubernetes.io/hostname
# Application specific configuration
app:
environment: production
logLevel: INFO
database:
host: postgresql
port: 5432
name: appdb
username: appuser
redis:
host: redis-master
port: 6379
# PostgreSQL configuration
postgresql:
enabled: true
auth:
postgresPassword: "postgres123"
username: "appuser"
password: "apppass123"
database: "appdb"
primary:
persistence:
enabled: true
size: 20Gi
storageClass: "gp2"
# Redis configuration
redis:
enabled: true
auth:
enabled: true
password: "redis123"
master:
persistence:
enabled: true
size: 8Gi
storageClass: "gp2"
Kubernetes Manifests
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "spring-boot-app.fullname" . }}
labels:
{{- include "spring-boot-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "spring-boot-app.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "spring-boot-app.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "spring-boot-app.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.app.environment }}
- name: SPRING_DATASOURCE_URL
value: "jdbc:postgresql://{{ .Values.app.database.host }}:{{ .Values.app.database.port }}/{{ .Values.app.database.name }}"
- name: SPRING_DATASOURCE_USERNAME
value: {{ .Values.app.database.username }}
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "spring-boot-app.fullname" . }}-secret
key: database-password
- name: SPRING_REDIS_HOST
value: {{ .Values.app.redis.host }}
- name: SPRING_REDIS_PORT
value: "{{ .Values.app.redis.port }}"
- name: SPRING_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "spring-boot-app.fullname" . }}-secret
key: redis-password
- name: LOGGING_LEVEL_ROOT
value: {{ .Values.app.logLevel }}
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
volumes:
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "spring-boot-app.fullname" . }}-config
labels:
{{- include "spring-boot-app.labels" . | nindent 4 }}
data:
application.yml: |
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
redis:
host: ${SPRING_REDIS_HOST}
port: ${SPRING_REDIS_PORT}
password: ${SPRING_REDIS_PASSWORD}
lettuce:
pool:
max-active: 20
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
logging:
level:
root: ${LOGGING_LEVEL_ROOT}
com.yourcompany: DEBUG
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
CI/CD Pipeline Entegrasyonu
GitLab CI/CD Pipeline
# .gitlab-ci.yml
stages:
- build
- test
- security
- package
- infrastructure
- deploy
- verify
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
DOCKER_TLS_CERTDIR: "/certs"
TF_ROOT: ${CI_PROJECT_DIR}/infrastructure
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/default
cache:
paths:
- .m2/repository/
- target/
build:
stage: build
image: maven:3.8.6-openjdk-17
script:
- mvn $MAVEN_CLI_OPTS compile
artifacts:
paths:
- target/
expire_in: 1 hour
test:
stage: test
image: maven:3.8.6-openjdk-17
services:
- postgres:15
- redis:7-alpine
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
SPRING_PROFILES_ACTIVE: test
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/testdb
SPRING_DATASOURCE_USERNAME: testuser
SPRING_DATASOURCE_PASSWORD: testpass
SPRING_REDIS_HOST: redis
script:
- mvn $MAVEN_CLI_OPTS test
artifacts:
reports:
junit:
- target/surefire-reports/TEST-*.xml
- target/failsafe-reports/TEST-*.xml
paths:
- target/site/jacoco/
coverage: '/Total.*?([0-9]{1,3})%/'
security_scan:
stage: security
image: owasp/dependency-check:latest
script:
- /usr/share/dependency-check/bin/dependency-check.sh
--project "$CI_PROJECT_NAME"
--scan target/
--format XML
--format JSON
--format HTML
artifacts:
reports:
dependency_scanning: dependency-check-report.json
paths:
- dependency-check-report.*
package:
stage: package
image: maven:3.8.6-openjdk-17
services:
- docker:20.10.16-dind
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- mvn $MAVEN_CLI_OPTS package -DskipTests
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
artifacts:
paths:
- target/*.jar
terraform_plan:
stage: infrastructure
image: hashicorp/terraform:1.6
before_script:
- cd $TF_ROOT
- terraform init
-backend-config="address=${TF_ADDRESS}"
-backend-config="lock_address=${TF_ADDRESS}/lock"
-backend-config="unlock_address=${TF_ADDRESS}/lock"
-backend-config="username=gitlab-ci-token"
-backend-config="password=${CI_JOB_TOKEN}"
-backend-config="lock_method=POST"
-backend-config="unlock_method=DELETE"
-backend-config="retry_wait_min=5"
script:
- terraform plan -var="app_version=$CI_COMMIT_SHA" -out=plan.tfplan
- terraform show -json plan.tfplan > plan.json
artifacts:
paths:
- $TF_ROOT/plan.tfplan
- $TF_ROOT/plan.json
expire_in: 1 week
only:
- merge_requests
- main
terraform_apply:
stage: infrastructure
image: hashicorp/terraform:1.6
before_script:
- cd $TF_ROOT
- terraform init
-backend-config="address=${TF_ADDRESS}"
-backend-config="lock_address=${TF_ADDRESS}/lock"
-backend-config="unlock_address=${TF_ADDRESS}/lock"
-backend-config="username=gitlab-ci-token"
-backend-config="password=${CI_JOB_TOKEN}"
-backend-config="lock_method=POST"
-backend-config="unlock_method=DELETE"
-backend-config="retry_wait_min=5"
script:
- terraform apply -var="app_version=$CI_COMMIT_SHA" -auto-approve
dependencies:
- terraform_plan
only:
- main
when: manual
deploy_staging:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: staging
url: https://staging-api.yourcompany.com
before_script:
- kubectl config use-context staging
script:
- helm upgrade --install staging-app ./helm-chart
--namespace staging
--create-namespace
--set image.tag=$CI_COMMIT_SHA
--set ingress.hosts[0].host=staging-api.yourcompany.com
--set app.environment=staging
--wait --timeout=10m
only:
- main
deploy_production:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: production
url: https://api.yourcompany.com
before_script:
- kubectl config use-context production
script:
- helm upgrade --install prod-app ./helm-chart
--namespace production
--create-namespace
--set image.tag=$CI_COMMIT_SHA
--set ingress.hosts[0].host=api.yourcompany.com
--set app.environment=production
--set replicaCount=5
--wait --timeout=15m
only:
- main
when: manual
health_check:
stage: verify
image: curlimages/curl:latest
script:
- sleep 30 # Wait for deployment to stabilize
- |
for i in {1..10}; do
if curl -f https://api.yourcompany.com/actuator/health; then
echo "Health check passed"
exit 0
fi
echo "Health check failed, attempt $i/10"
sleep 30
done
echo "Health check failed after 10 attempts"
exit 1
only:
- main
dependencies:
- deploy_production
IaC, modern yazılım geliştirme süreçlerinin ayrılmaz bir parçasıdır. Terraform, Ansible ve Kubernetes gibi araçlarla birlikte kullanıldığında güçlü, ölçeklenebilir ve güvenilir altyapı yönetimi sağlar.