Skip to content

Containers (Docker) ​

Container, uygulamayı aynı imajla build, test ve deploy edilebilir hale getiren paketleme sınırıdır. Docker kullanmanın amacı makine bağımlılığını azaltmaktır; yanlış kullanılırsa sadece daha taşınabilir bir karmaşa üretir.

Hızlı Karar ​

İhtiyaçYaklaşımDikkat
Tekrarlanabilir deployMulti-stage DockerfileBuild ve runtime image ayrılmalı
Küçük ve güvenli imajMinimal base image, non-root userDebug araçları eksik olabilir
Lokal çoklu servis geliştirmeDocker ComposeProd orkestrasyon yerine geçmez
Hızlı startupLayer cache ve Spring Boot layeringBüyük image cold start'ı uzatır

Üretim Kontrol Listesi ​

  • Problem: Container hangi ortam farkını veya deploy sorununu çözüyor?
  • Çözüm: Image tag, health check, resource limit, secret injection ve shutdown davranışı tanımlı mı?
  • Trade-off: Container taşınabilirlik sağlar; image güvenliği ve registry yönetimi ek iş getirir.
  • Hata durumu: OOMKill, crash loop, port conflict, disk dolması ve signal handling test edilmeli.
  • Ölçüm: Image size, startup time, memory/CPU limit kullanımı, restart count ve vulnerability count izlenmeli.
  • Güvenlik/maliyet: Root user, gereksiz paket ve sabit secret yasaklanmalı; büyük image registry ve network maliyeti üretir.

Giriş ​

Containerlar, uygulamaların ve bağımlılıklarının izole edilmiş ortamlarda çalıştırılması için kullanılan hafif sanallaştırma teknolojisidir. Docker, bu teknolojinin en yaygın kullanılan implementasyonudur.

Docker Mimarisi ​

Container Yaşam Döngüsü ​

Docker Temel Kavramlar ​

1. Docker Architecture ​

dockerfile
# Multi-stage Dockerfile örneği
FROM openjdk:11-jdk-slim AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/target/app.jar app.jar
EXPOSE 8080
USER 1000:1000
ENTRYPOINT ["java", "-jar", "app.jar"]

Best practices Dockerfile yazımı:

dockerfile
# Base image optimizasyonu
FROM alpine:3.15 AS base

# Security: Non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Package installation ve cleanup
RUN apk add --no-cache \
    openjdk11-jre \
    && rm -rf /var/cache/apk/*

# Working directory
WORKDIR /app

# Copy application
COPY --chown=appuser:appgroup target/app.jar app.jar

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# Switch to non-root user
USER appuser

# Startup command
ENTRYPOINT ["java", "-Xmx512m", "-Xms256m", "-jar", "app.jar"]

2. Docker Compose Configuration ​

Multi-service application örneği:

yaml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DATABASE_URL=jdbc:postgresql://db:5432/appdb
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - app-network
    volumes:
      - app-logs:/var/log/app
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - app-network
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    networks:
      - app-network
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  app-logs:

networks:
  app-network:
    driver: bridge

Container Runtime Configuration ​

1. Docker Security Configuration ​

Security best practices Spring Boot uygulaması için:

java
@Configuration
@EnableWebSecurity
public class DockerSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .frameOptions().deny()
                .contentTypeOptions().and()
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubdomains(true)
                    .preload(true)
                )
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        
        return http.build();
    }
}

Container resource monitoring:

java
@Component
public class ContainerMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    
    public ContainerMetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        initializeMetrics();
    }
    
    private void initializeMetrics() {
        // Memory metrics
        Gauge.builder("container.memory.usage")
                .description("Container memory usage")
                .register(meterRegistry, this, ContainerMetricsCollector::getMemoryUsage);
        
        // CPU metrics
        Gauge.builder("container.cpu.usage")
                .description("Container CPU usage")
                .register(meterRegistry, this, ContainerMetricsCollector::getCpuUsage);
        
        // Disk metrics
        Gauge.builder("container.disk.usage")
                .description("Container disk usage")
                .register(meterRegistry, this, ContainerMetricsCollector::getDiskUsage);
    }
    
    private double getMemoryUsage(ContainerMetricsCollector collector) {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        return (double) (totalMemory - freeMemory) / totalMemory * 100;
    }
    
    private double getCpuUsage(ContainerMetricsCollector collector) {
        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
        if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
            return ((com.sun.management.OperatingSystemMXBean) osBean).getProcessCpuLoad() * 100;
        }
        return 0.0;
    }
    
    private double getDiskUsage(ContainerMetricsCollector collector) {
        File root = new File("/");
        long totalSpace = root.getTotalSpace();
        long usableSpace = root.getUsableSpace();
        return (double) (totalSpace - usableSpace) / totalSpace * 100;
    }
}

2. Container Networking ​

Network bridge configuration:

bash
#!/bin/bash

# Custom Docker network oluşturma
docker network create --driver bridge \
  --subnet=172.20.0.0/16 \
  --ip-range=172.20.240.0/20 \
  --gateway=172.20.0.1 \
  app-network

# Network security policies
docker network create --driver bridge \
  --opt com.docker.network.bridge.enable_icc=false \
  --opt com.docker.network.bridge.enable_ip_masquerade=true \
  secure-network

Service discovery Docker Compose ile:

java
@Service
public class ServiceDiscoveryService {
    
    @Value("${service.registry.url:http://consul:8500}")
    private String registryUrl;
    
    @Autowired
    private RestTemplate restTemplate;
    
    public List<ServiceInstance> discoverServices(String serviceName) {
        try {
            String url = registryUrl + "/v1/health/service/" + serviceName + "?passing=true";
            ConsulHealthResponse response = restTemplate.getForObject(url, ConsulHealthResponse.class);
            
            return response.getServices().stream()
                    .map(this::mapToServiceInstance)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("Service discovery failed for service: {}", serviceName, e);
            return Collections.emptyList();
        }
    }
    
    private ServiceInstance mapToServiceInstance(ConsulService consulService) {
        return ServiceInstance.builder()
                .serviceId(consulService.getService().getId())
                .serviceName(consulService.getService().getService())
                .host(consulService.getService().getAddress())
                .port(consulService.getService().getPort())
                .metadata(consulService.getService().getMeta())
                .build();
    }
}

Container Image Management ​

1. Registry Integration ​

Docker Registry authentication ve push/pull işlemleri:

java
@Service
public class DockerRegistryService {
    
    @Value("${docker.registry.url}")
    private String registryUrl;
    
    @Value("${docker.registry.username}")
    private String username;
    
    @Value("${docker.registry.password}")
    private String password;
    
    public void pushImage(String imageName, String tag) {
        try {
            ProcessBuilder pb = new ProcessBuilder(
                "docker", "push", registryUrl + "/" + imageName + ":" + tag
            );
            
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode != 0) {
                throw new RuntimeException("Docker push failed with exit code: " + exitCode);
            }
            
            log.info("Successfully pushed image: {}/{}}:{}", registryUrl, imageName, tag);
        } catch (Exception e) {
            log.error("Failed to push image: {}", imageName, e);
            throw new RuntimeException("Image push failed", e);
        }
    }
    
    public boolean pullImage(String imageName, String tag) {
        try {
            ProcessBuilder pb = new ProcessBuilder(
                "docker", "pull", registryUrl + "/" + imageName + ":" + tag
            );
            
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            return exitCode == 0;
        } catch (Exception e) {
            log.error("Failed to pull image: {}:{}", imageName, tag, e);
            return false;
        }
    }
}

Image vulnerability scanning entegrasyonu:

java
@Component
public class ImageSecurityScanner {
    
    @Autowired
    private DockerClient dockerClient;
    
    public SecurityScanResult scanImage(String imageId) {
        try {
            // Trivy scanner kullanarak vulnerability check
            ProcessBuilder pb = new ProcessBuilder(
                "trivy", "image", "--format", "json", "--output", "/tmp/scan-result.json", imageId
            );
            
            Process process = pb.start();
            process.waitFor();
            
            // Scan sonuçlarını parse et
            String scanOutput = Files.readString(Paths.get("/tmp/scan-result.json"));
            ObjectMapper mapper = new ObjectMapper();
            TrivyScanResult trivyResult = mapper.readValue(scanOutput, TrivyScanResult.class);
            
            return mapToSecurityScanResult(trivyResult);
        } catch (Exception e) {
            log.error("Image security scan failed for image: {}", imageId, e);
            throw new SecurityScanException("Security scan failed", e);
        }
    }
    
    private SecurityScanResult mapToSecurityScanResult(TrivyScanResult trivyResult) {
        List<Vulnerability> vulnerabilities = trivyResult.getResults().stream()
                .flatMap(result -> result.getVulnerabilities().stream())
                .map(this::mapVulnerability)
                .collect(Collectors.toList());
        
        return SecurityScanResult.builder()
                .imageId(trivyResult.getArtifactName())
                .scanDate(Instant.now())
                .totalVulnerabilities(vulnerabilities.size())
                .criticalCount(countBySeverity(vulnerabilities, "CRITICAL"))
                .highCount(countBySeverity(vulnerabilities, "HIGH"))
                .mediumCount(countBySeverity(vulnerabilities, "MEDIUM"))
                .lowCount(countBySeverity(vulnerabilities, "LOW"))
                .vulnerabilities(vulnerabilities)
                .build();
    }
}

2. Container Lifecycle Management ​

Health check implementation:

java
@RestController
@RequestMapping("/actuator")
public class ContainerHealthController {
    
    @Autowired
    private DatabaseHealthIndicator databaseHealth;
    
    @Autowired
    private RedisHealthIndicator redisHealth;
    
    @GetMapping("/health")
    public ResponseEntity<HealthStatus> health() {
        HealthStatus status = HealthStatus.builder()
                .status("UP")
                .timestamp(Instant.now())
                .checks(performHealthChecks())
                .build();
        
        boolean allHealthy = status.getChecks().values().stream()
                .allMatch(check -> "UP".equals(check.getStatus()));
        
        return ResponseEntity.status(allHealthy ? 200 : 503).body(status);
    }
    
    @GetMapping("/health/liveness")
    public ResponseEntity<Map<String, String>> liveness() {
        // Basic liveness check - application is running
        return ResponseEntity.ok(Map.of(
                "status", "UP",
                "timestamp", Instant.now().toString()
        ));
    }
    
    @GetMapping("/health/readiness")
    public ResponseEntity<Map<String, Object>> readiness() {
        Map<String, HealthCheck> checks = performHealthChecks();
        boolean ready = checks.values().stream()
                .allMatch(check -> "UP".equals(check.getStatus()));
        
        return ResponseEntity.status(ready ? 200 : 503)
                .body(Map.of(
                        "status", ready ? "UP" : "DOWN",
                        "checks", checks,
                        "timestamp", Instant.now()
                ));
    }
    
    private Map<String, HealthCheck> performHealthChecks() {
        Map<String, HealthCheck> checks = new HashMap<>();
        
        checks.put("database", databaseHealth.health());
        checks.put("redis", redisHealth.health());
        checks.put("disk", checkDiskSpace());
        checks.put("memory", checkMemoryUsage());
        
        return checks;
    }
    
    private HealthCheck checkDiskSpace() {
        File root = new File("/");
        long usableSpace = root.getUsableSpace();
        long totalSpace = root.getTotalSpace();
        double usagePercent = (double) (totalSpace - usableSpace) / totalSpace * 100;
        
        String status = usagePercent < 90 ? "UP" : "DOWN";
        return HealthCheck.builder()
                .status(status)
                .details(Map.of(
                        "usagePercent", usagePercent,
                        "threshold", 90
                ))
                .build();
    }
}

Container monitoring ve logging:

java
@Component
public class ContainerMonitoringService {
    
    private final MeterRegistry meterRegistry;
    private final Logger structuredLogger = LoggerFactory.getLogger("CONTAINER_METRICS");
    
    @EventListener
    public void handleContainerEvent(ContainerEvent event) {
        structuredLogger.info("Container event: type={}, containerId={}, timestamp={}", 
                event.getType(), event.getContainerId(), event.getTimestamp());
        
        // Metrics recording
        Timer.Sample sample = Timer.start(meterRegistry);
        sample.stop(Timer.builder("container.event.duration")
                .tag("event.type", event.getType())
                .register(meterRegistry));
        
        meterRegistry.counter("container.events.total",
                "type", event.getType(),
                "status", event.getStatus())
                .increment();
    }
    
    @Scheduled(fixedRate = 30000)
    public void collectContainerMetrics() {
        try {
            ContainerStats stats = dockerClient.stats(getCurrentContainerId()).exec();
            
            // Memory metrics
            long memoryUsage = stats.getMemoryStats().getUsage();
            long memoryLimit = stats.getMemoryStats().getLimit();
            double memoryPercent = (double) memoryUsage / memoryLimit * 100;
            
            meterRegistry.gauge("container.memory.usage.bytes", memoryUsage);
            meterRegistry.gauge("container.memory.usage.percent", memoryPercent);
            
            // CPU metrics
            long cpuDelta = stats.getCpuStats().getCpuUsage().getTotalUsage() - 
                           stats.getPreCpuStats().getCpuUsage().getTotalUsage();
            long systemDelta = stats.getCpuStats().getSystemCpuUsage() - 
                              stats.getPreCpuStats().getSystemCpuUsage();
            
            double cpuPercent = (double) cpuDelta / systemDelta * 100;
            meterRegistry.gauge("container.cpu.usage.percent", cpuPercent);
            
            // Network metrics
            Map<String, NetworkStats> networks = stats.getNetworks();
            networks.forEach((interfaceName, networkStats) -> {
                meterRegistry.gauge("container.network.rx.bytes", 
                        Tags.of("interface", interfaceName), networkStats.getRxBytes());
                meterRegistry.gauge("container.network.tx.bytes", 
                        Tags.of("interface", interfaceName), networkStats.getTxBytes());
            });
            
        } catch (Exception e) {
            log.error("Failed to collect container metrics", e);
        }
    }
}

Bu kapsamlı Docker ve Container implementasyonu, production-ready containerized uygulamalar geliştirmek için gereken tüm bileşenleri sağlar.

Eren Demir tarafından oluşturulmuştur.