Docker Compose
GÖRSELDEKI QR KODU KULLANARAK ILETISIM KANALLARIMA ULASABILIRSINIZ.
YAPACAĞIMIZ PROJE
Java 21 ve Spring Boot 3.2.1 ile bir proje yazacağız. Bu projeye docker-compose dosyası ekleyeceğiz. ekleyeceğimiz bu docker-compose dosyasında redis ve postgresql olacak. Projeyi ayağa kaldırdığımızda docker otomatik olarak hem redis’in hem de postgresql’in image’larından bir container oluşturup bize sunacak.
1) Docker Compose Tanımı
Compose kelime anlamı olarak oluşturmak, yazmak gibi anlamlar taşır.
Docker dünyasında ise tanım olarak, “multi-container” docker uygulamalarını tanımlamak ve çalıştırmak için kullanılan bir araçtır.
Multi-container: Bu terim tek bir uygulamanın birden fazla docker container’ı kullanarak çalışması anlamına gelir.
Compose, uygulamanın servislerini yapılandırmak için bir YAML dosyası kullanır. Sonrasında tek bir komutla tüm servisleri YAML içerisine verdiğimiz konfigürasyonlarıyla oluşturacak şekilde başlatır.
Compose her environmente uyumlu bir şekilde çalışabilir. (prod, dev, test)
2) Docker Compose Tanımlamak ve Kullanmak
Docker Compose’u kullanmak 3 adımlı bir işlemdir.
1.ADIM:
Uygulamanın environmentini bir dockerfile ile tanımlayın. Bu sayede istediğiniz her yerde üretilebilir.
Dockerfile: Bir dockerfile, bir uygulamanın nasıl oluşturulacağı ve çalıştırılacağı hakkında talimatlar içeren bir dosyadır. Bu dosya, uygulamanızın çalışma ortamını tanımlar. (İşletim sistemi, programlama dili sürümleri, kütüphaneler, ortam değişkenleri v.s.) Dockerfile kullanarak, uygulama herhangi bir sistemde ya da cloud ortamında aynı şekilde sorunsuz bir şekilde çalıştırılabilir. Bu yaklaşım farklı ortamlarda (dev, test, prod) tutarlı bir şekilde çalışmasını sağlar. Bu durum da aslında “lokalimde çalışıyordu” cevabını olabildiğince minimuma indirir. (:
Docker, bu dockerfile’daki talimatları ,zleyerek b,r Docker imajı oluşturur. Ve bu imaj daha sonra farklı makinelerde Docker containerları olarak çalıştırılabilir. Bu sayede; uygulamanın, herhangi bir yerde aynı konfiglerle yeniden üretilebilmesi sağlanmış olur.
2.ADIM:
İzole edilmiş bir ortamda birlikte çalıştırılabilmeleri içi uygulamayı oluşturan servisleri bir compose.yaml dosyasında tanımlayın.
3.ADIM
docker-compose dosyasının bulunduğu dizinde docker compose up komutu ile compose dosyanızı çalıştırın
3) Yaygın Use Case’ler
- Uygulamadaki her bir bağımlılık için (database, queue, cache v.s.) bir veya daha fazla containerı tek bir komutla oluşturabilir.
- Herhangi bir CI/CD sürecinin önemli bir parçası otomatize edilmiş test paketidir. Otomatik, uçtan uca testler, testlerin yürütüleceği bir ortam gerektirir. Compose, test paketi için yalıtılmış test ortamları oluşturmanın ve yok etmenin kullanışlı bir yolunu sağlar.
4) Uygulamaya Geçelim
1.ADIM
Intellij IDE’nin Spring Initializr’ını kullanarak bir proje başlatıyorum.
Kullanacağım versionlar
* Java 21 ve Spring Boot 3.2.1
Bağımlılıkları oluştururken, eskiden var mıydı bilmiyorum ama, karşıma bu şekilde bir yapı çıktı. Sol taraftaki mor kutunun içine docker compose bağımlılığını rahat bir şekilde ekleyebildim ve bu eklediğim bağımlılık sağ taraftaki sarı kutu içerisinde görebildim.
Daha detaylı bilgi almak için bu cümleye tıklayabilirsiniz.
Ve “create” butonuna basarak projemizi oluşturabiliriz.
2.ADIM
Projeyi create ettiğimde, sol taraftaki siyah kutu içerisinde mavi ile işaretlenmiş compose.yml dosyasını benim için hazırladı ve ve içerisine eklediğim bağımlılıkların boilerplate kodlarını benim için verdi. (Intellij’i çok seviyorum.)
Şimdi buradaki kodları aşağıdaki kodlarla değiştirip her birisini tek tek açıklamasına bakalım.
services:
postgres:
container_name: example-docker-compose-postgresql
image: 'postgres:latest'
environment:
- 'POSTGRES_DB=docker-compose-example'
- 'POSTGRES_PASSWORD=root'
- 'POSTGRES_USER=postgres'
ports:
- '1453:5432'
expose:
- '1453'
redis:
container_name: example-docker-compose-redis-cache
image: 'redis:latest'
ports:
- '1923:6379'
expose:
- '1923'
services
altına oluşmasını istediğimiz hizmetlerimizi veriyoruz.postgres
altında container_name dediğimiz zaman bu container’ın adını belirtiyoruz. Ki aynısınıredis
servisi altında da kullandık. Yani postgres’imiz, example-docker-compose-postgresql adıyla ayağa kalkacak, redis’imiz ise example-docker-compose-redis-cache adıyla ayağa kalkacak.image
ile bu container’ımızın hangi image’dan ayağa kalkacağını söylüyoruz.environment
‘ te ise kullanacağımız servisin environment ayarlarını veriyoruz. postgres servisi için aslında şunları yapmasını söyledik. “Git, docker_compose_example diye bir db oluştur. Şifren root, kullanıcın da postgres.” Tabi burada dikkat edilmesi gereken nokta, sizin kendi postgresql şifrenizi ve user’ınızı vermeniz lazım.ports
Burada yaptığımız işlem şu. Host makinenin 1453 portunu container’ın 5432 portuna bağlar. Bu, PostgreSQL veritabanına host makineden 1453 portu üzerinden erişim sağlar.expose
ile de şunu amaçladık. Bu, container içindeki 1453 portunu Docker iç ağında diğer servislerin erişimine açar.
3.ADIM
Uygulamamız için gerekli olan application.yml dosyamızı oluşturup düzenleyelim.
server:
port: 7575
spring:
datasource:
url: jdbc:postgresql://localhost:1453/docker-compose-postgres
username: postgres
password: root
driverClassName: org.postgresql.Driver
jpa:
hibernate:
ddlAuto: create-drop
showSql: true
properties:
hibernate:
format_sql: true
database: postgresql
databasePlatform: org.hibernate.dialect.PostgreSQLDialect
data:
redis:
host: localhost
port: 1923
Buradaki konfigleri açıklamak gerekirse;
- uygulamamız 7575 portundan ayağa kalkacak.
- spring > datasource altında postgresql url’imizi verdik. Normalde PostgreSQL için default port 5432 ama biz compose dosyamızda 1453 portunu expose ettiğimiz için buraya 1453 portunu verdik. Ve username,password ve driver class name’imizi parametre olarak verdik.
- JPA > hibernate altında, ddlAuto ile database’imizin oluşma şeklini belirledik ve gerekli olan diğer util konfigleri verdik.
- data > redis altında ise docker üzerinde ayağa kalkan redis’imizin bağlantı konfigürasyonlarını verdik. (Eski yazılarımda redis’in application.yml’ına bakarsanız buradakilerle farklı. Çünkü onlar deprecated olduğu için bu şekilde kullandım.)
4.ADIM
mockaroo ile fake datalar oluşturup bu fake datayı önce projemizin resources klasörüne almamız ve sonrasında isebu fake data için bir Entity class’ı oluşturmamız gerekiyor.
package org.fsk.dockercompose.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
@Entity(name = "Person")
@Table(name = "person")
@Data
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private int age;
private String email;
private String companyName;
private String country;
}
5.ADIM
Hemen repository, service ve controller paketlerini oluşturup içlerine class’larımızı ekleyelim.
// CONTROLLER Paketi PersonController classı
//---------------------------------------------//
package org.fsk.dockercompose.controller;
import lombok.RequiredArgsConstructor;
import org.fsk.dockercompose.entity.Person;
import org.fsk.dockercompose.service.PersonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
@RequestMapping("/person")
@RestController
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping("/load")
public void loadDataFromJsonFileToDb() throws IOException {
personService.loadData();
}
@GetMapping("/getFromDb/{city}/{age}")
public List<Person> getFromDb(@PathVariable("city") String city, @PathVariable("age") int age) {
return personService.getPeopleByCityAndAge(city, age);
}
}
// SERVICE Paketi PersonService classı
//---------------------------------------------//
package org.fsk.dockercompose.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.fsk.dockercompose.entity.Person;
import org.fsk.dockercompose.repository.PersonRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Service
@RequiredArgsConstructor
public class PersonService {
private final PersonRepository personRepository;
@Transactional
public void loadData() throws IOException {
ObjectMapper mapper = new ObjectMapper();
File file = new ClassPathResource("mock-data.json").getFile();
List<Person> persons = mapper.readValue(file, new TypeReference<>() {});
personRepository.saveAll(persons);
}
@Cacheable(value = "PersonCache", key = "#city + '_' + #age")
public List<Person> getPeopleByCityAndAge(String city, int age) {
return personRepository.findAllByCountryAndAgeGreaterThanEqual(city, age);
}
}
// Repository Paketi PersonService classı
//---------------------------------------------//
package org.fsk.dockercompose.repository;
import org.fsk.dockercompose.entity.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findAllByCountryAndAgeGreaterThanEqual(String country, int age);
}
Service katmanı içerisindeki
@Transactional
public void loadData() throws IOException {
ObjectMapper mapper = new ObjectMapper();
File file = new ClassPathResource("mock-data.json").getFile();
List<Person> persons = mapper.readValue(file, new TypeReference<>() {});
personRepository.saveAll(persons);
}
bu kod aslında projemizin altında mockaroo’dan gelen mock-data.json dosyasını okur ve db’ye yazar.
6.ADIM
Şimdi Redis Cache için gerekli configuration paketimizi ve class’larımızı ekleyelim.
// configuration Paketi RedisCfg classı
//---------------------------------------------//
@Configuration
public class RedisCfg {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration =
new RedisStandaloneConfiguration();
redisConfiguration.setHostName(redisHost);
redisConfiguration.setPort(redisPort);
return new LettuceConnectionFactory(redisConfiguration);
}
}
// configuration Paketi CacheConfig classı
//---------------------------------------------//
package org.fsk.dockercompose.configuration;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
@Configuration
@EnableCaching
@RequiredArgsConstructor
public class CacheConfig {
private final RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(
new GenericJackson2JsonRedisSerializer())
);
}
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
.builder(redisConnectionFactory)
.withCacheConfiguration("PersonCache", cacheConfiguration());
return builder.build();
}
}
Buradaki kodların detaylarını ve daha geniş bilgiyi buraya tıklayarak öğrenebilirsiniz.
7.ADIM
Uygulama aslında bu kadar. Yani bu kadar teferruata da gerek yoktu ama, daha iyi anlaşılabilmesi açısından bu şekilde yazdım. Şimdi terminal ekranına gidip sadece compose dosyamızı çalıştıralım. Ama lütfen uygulamanın kendisini çalıştırmayalım.
Bunun için intellij ide’de projedeki compose dosyasının bulunduğu dizinde docker compose -p docker-compose-example up
diyerek compose dosyamızı çalıştırabiliriz. -p parametresinden sonra dilediğimiz ismi verebiliriz ama, bu sefer docker bu copose dosyasını default isimle çalıştırır.
Burada yeşil kutu içerisinde her ikisinin de ayağa kalktığını gördük. Ve aslında yazdığımız uygulamadan bağımsız olarak çalıştırdığımız docker compose dosyası çalıştı.
8.ADIM
Şimdi docker compose çalışırken uygulamayı çalıştıralım.
Uygulama çalışmadı çünkü redis için aynı id değeri ile bir container’ın zaten çalıştığını söyledi.
9.ADIM
Şimdi docker desktop üzerinden bütün oluşturduğumuz containerları silelim. Aşağıdaki siyah ve yeşil kutular içerisindeki butonlar ile bu işlemi rahatlıkla yapabiliriz.
Şimdi hiç bir şey yapmadan uygulamaya gelip tekrar run butonuna basalım.
Ve göreceğiz ki, docker dosyasını çalıştırmadan uygulamamız ayağa kalktı, bununla birlikte de compose dosyası içerisine tanımladığımız servisler de ayağa kalktı.
Requestlerimizi gönderelim.
Önce /load requestini çalıştırdık.
Docker üzerinde çalıştırdığımız postgreSql’i Intellij üzerinden data source’a bağladığımız zaman verilerin rahat bir şekilde geldiğini gördük.
localhost:7575/person/getFromDb/China/25
yukarıdaki şekilde bir request atıp ise redis cache’i kontrol edelim.
Yine intellij üzerinden docker üzerinde çalışan redis’e bağlanıp sorgularımızı attığımız zaman doğru bir şekilde çalıştığını gördük.
redis config’de ttl değerimizi 5dk verdiğimiz için ise, 5 dk sonra bu sorgular boş değer atacaktır.
5) Yaygın Kullanılan Docker Compose Komutları
build:
Dockerfile’dan bir imaj oluşturmak için kullanılır.context
vedockerfile
parametreleri ile yol ve dockerfile belirtilebilir.
services:
app:
build: .
volumes:
Veri kalıcılığı ve veri paylaşımı için kullanılır. Host ve container arasında dosya sistemi bağlantısı sağlar.
services:
db:
volumes:
- "./data:/var/lib/postgresql/data"
depends_on:
Servisler arasındaki bağımlılıkları belirtir. Bu, servislerin başlatılma sırasını kontrol etmeye yardımcı olur.
services:
web:
depends_on:
- db
networks:
Özel ağlar oluşturmak ve servisleri bu ağlara bağlamak için kullanılır.
networks:
mynet:
services:
web:
networks:
- mynet
command:
Container başlatıldığında çalıştırılacak komutu belirtir.
services:
web:
command: ["nginx", "-g", "daemon off;"]
EKSTRA ADIM: ALIŞTIRMA YAPMAK İSTERSENİZ DİYE.. ((((:
Eğer bu konuyla alakalı alıştırma yapmak isterseniz, bu makalemi okuyup, şu an okuduğunuz makale ile harmanlayabilirsiniz. Yani, redis ve mongodb bir docker compose üzerinden ayağa kalkacak şekilde yapabilirsiniz. (:
Okuduğunuz için teşekkür ederim.
Projenin kaynak kodlarına buradan ulaşabilirsiniz.