1. Kurmak istediğimiz sistem: tek repo, dört servis, bir gateway, tek EC2 hedefi
Bu senaryoda elimizde tek bir GitHub reposu var. Repo içinde dört servis tutuyoruz: `api-gateway`, `user-service`, `order-service`, `catalog-service`. İstekler dışarıdan önce gateway'e geliyor, oradan iç servislere yönleniyor.
CI/CD hedefimiz şu: GitHub'a push yaptığımızda servisler build edilsin, image'lar `ghcr.io` altına gitsin, sonra EC2 makinesinde yeni image'lar çekilip Docker Compose ile servisler ayağa kalksın.
Neden tek repo?
- Servisler arası ortak versiyon yönetimi kolaylaşır.
- Gateway ve backend modüllerini birlikte değiştirmek pratiktir.
- Ortak library, DTO ve build mantığı paylaşılabilir.
Neden EC2?
- Basit ve doğrudan kontrol sağlar.
- Kubernetes kadar ağır değildir.
- Küçük ve orta ölçekli projede maliyet yönetimi nettir.
2. Multi-module Spring Boot yapısı nasıl kurulur?
Spring'in multi-module yaklaşımında kökte bir parent proje bulunur; servisler alt modül olarak tanımlanır. Ortak kod gerekiyorsa ayrıca `common` gibi bir modül açılır. Burada her servis ayrı çalıştırılabilir Spring Boot uygulamasıdır.
commerce-platform/
├── pom.xml
├── common/
│ └── pom.xml
├── api-gateway/
│ └── pom.xml
├── user-service/
│ └── pom.xml
├── order-service/
│ └── pom.xml
└── catalog-service/
└── pom.xml
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.5</version>
</parent>
<groupId>com.dogankaya</groupId>
<artifactId>commerce-platform</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>api-gateway</module>
<module>user-service</module>
<module>order-service</module>
<module>catalog-service</module>
</modules>
</project>
Root `pom.xml` uygulama değil, yöneticidir. Asıl runnable Spring Boot uygulamaları alt modüllerdir. Her servis kendi `spring-boot-starter-web` veya gateway dependency setine sahip olur.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
Spring Cloud Gateway, Spring Framework 7 ve Spring Boot 4 tabanlı route, security, monitoring ve resiliency katmanını gateway üzerinde toplamak için uygundur. Böylece iç servislerde her yerde aynı cross-cutting işi tekrar tekrar yazmazsın.
3. Docker tarafı: her servis için image üretmek ve Compose ile bir araya getirmek
Her Spring Boot servisi için ayrı bir image üreteceğiz. En temiz yaklaşım her modülün kendi Dockerfile dosyasına sahip olmasıdır. Build sonrası çıkan jar dosyası image içine kopyalanır ve servis tek bir `ENTRYPOINT` ile ayağa kalkar.
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/user-service-1.0.0.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
services:
api-gateway:
image: ghcr.io/kayadogan1/commerce-platform-api-gateway:latest
ports:
- "80:8080"
depends_on:
- user-service
- order-service
- catalog-service
user-service:
image: ghcr.io/kayadogan1/commerce-platform-user-service:latest
environment:
SPRING_PROFILES_ACTIVE: prod
order-service:
image: ghcr.io/kayadogan1/commerce-platform-order-service:latest
environment:
SPRING_PROFILES_ACTIVE: prod
catalog-service:
image: ghcr.io/kayadogan1/commerce-platform-catalog-service:latest
environment:
SPRING_PROFILES_ACTIVE: prod
`application-prod.yml`, `.env` veya compose içinde asla gerçek AWS access key, database password veya GitHub token yazma. Bu yazıda da özellikle placeholder isimler kullanıyoruz: `AWS_ACCESS_KEY_ID`, `DB_PASSWORD`, `GHCR_PAT` gibi.
4. GitHub Container Registry neden kullanılır ve nasıl etiketlenir?
GitHub'ın eski Docker registry yapısı yerini `ghcr.io` tabanlı Container Registry'ye bıraktı. Burada her servis image'ını GitHub repo ile ilişkili şekilde saklayabilir, GitHub Actions içinden `GITHUB_TOKEN` ile publish edebilirsin.
ghcr.io/kayadogan1/commerce-platform-api-gateway:latest
ghcr.io/kayadogan1/commerce-platform-user-service:latest
ghcr.io/kayadogan1/commerce-platform-order-service:latest
ghcr.io/kayadogan1/commerce-platform-catalog-service:latest
5. AWS EC2 hazırlığı: Ubuntu, Docker kurulumu ve deploy kullanıcı düzeni
EC2 üzerinde en pratik yaklaşım Ubuntu tabanlı bir instance açmak, Docker Engine kurmak ve uygulama klasörünü tek bir deploy kullanıcısı altında yönetmektir. Burada asıl önemli nokta şudur: server'a girebilmek için SSH key kullanırsın, ama workflow içine hiçbir zaman gerçek private key'i dosya olarak commit etmezsin.
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo ${UBUNTU_CODENAME:-$VERSION_CODENAME}) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
/opt/apps/commerce-platform/
├── docker-compose.prod.yml
├── .env
└── deploy.sh
AWS access key'i EC2 içine düz metin gömmüyoruz. EC2 zaten kendi rolüyle AWS servislerine erişecekse IAM role tercih edilir. GitHub Actions için de `EC2_HOST`, `EC2_USER`, `EC2_SSH_KEY` gibi secret alanları kullanılır; gerçek değer yazılmaz.
6. GitHub Actions CI/CD akışı: build, test, image push ve EC2 deploy
Pipeline iki mantık taşır. Birinci adım CI: Maven build ve test. İkinci adım CD: Docker image üret, GHCR'ye push et, sonra EC2'ye SSH ile bağlanıp yeni image'ları çek ve compose ile yeniden başlat.
name: Build and Deploy
on:
push:
branches: [ "main" ]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Build
run: mvn clean verify
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push gateway
uses: docker/build-push-action@v6
with:
context: ./api-gateway
push: true
tags: ghcr.io/kayadogan1/commerce-platform-api-gateway:latest
- name: Build and push user-service
uses: docker/build-push-action@v6
with:
context: ./user-service
push: true
tags: ghcr.io/kayadogan1/commerce-platform-user-service:latest
- name: Build and push order-service
uses: docker/build-push-action@v6
with:
context: ./order-service
push: true
tags: ghcr.io/kayadogan1/commerce-platform-order-service:latest
- name: Build and push catalog-service
uses: docker/build-push-action@v6
with:
context: ./catalog-service
push: true
tags: ghcr.io/kayadogan1/commerce-platform-catalog-service:latest
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /opt/apps/commerce-platform
echo "${{ secrets.GHCR_PULL_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
docker image prune -f
CI tarafında
- `mvn clean verify` ile testler çalışır.
- Her servis için image build edilir.
- Image'lar GHCR'ye `latest` veya SHA tag ile pushlanır.
CD tarafında
- EC2'ye SSH ile bağlanılır.
- Yeni image'lar `docker compose pull` ile çekilir.
- Servisler `up -d` ile yeniden ayağa kaldırılır.
7. Secret ve güvenlik best practice'leri: key sızdırmadan deploy kurmak
Bu tip yazılarda en büyük risk, eğitim amaçlı örnek verirken gerçek secret gösterilmesidir. Burada özellikle tüm alanları placeholder bıraktık. Gerçek hayatta GitHub Actions `Secrets and variables` ekranını kullanırsın ve değerleri oradan çekersin.
Bu akış ilk aşama için yeterli. Bir sonraki olgunlaştırma adımı health check, reverse proxy, zero-downtime rollout, image digest pinning ve environment bazlı compose override dosyaları olur.
Kaynakça
Bu yazıdaki multi-module yapı, gateway tercihi, GHCR davranışı, Docker kurulumu ve GitHub Actions deployment akışı aşağıdaki resmi dokümanlar temel alınarak derlendi.
- Spring Getting Started: Creating a Multi Module Project — Spring Boot çok modüllü parent/module yapısının temel modeli.
- Spring Cloud Gateway Reference — Gateway'in Spring Boot 4 ve Spring Framework 7 tabanlı kullanım çerçevesi.
- GitHub Docs: Working with the Container registry — GHCR authentication, push/pull ve `GITHUB_TOKEN` kullanımı.
- GitHub Docs: Deploying with GitHub Actions — deployment workflow mantığı ve GitHub Actions deploy rehberi.
- Docker Docs: Install Docker Engine on Ubuntu — EC2 Ubuntu makinede resmi Docker Engine kurulumu.