diff --git a/.github/workflows/deploy-with-docker-prod.yml b/.github/workflows/deploy-with-docker-prod.yml index 528eedc5..18bdb9d0 100644 --- a/.github/workflows/deploy-with-docker-prod.yml +++ b/.github/workflows/deploy-with-docker-prod.yml @@ -77,6 +77,8 @@ jobs: echo "MINIO_ACCESS_KEY=${{ secrets.PROD_MINIO_ACCESS_KEY }}" >> .env.prod echo "MINIO_SECRET_KEY=${{ secrets.PROD_MINIO_SECRET_KEY }}" >> .env.prod echo "MINIO_BUCKET=${{ secrets.PROD_MINIO_BUCKET}}" >> .env.prod + echo "KAFKA_BOOTSTRAP_SERVERS=${{secrets.PROD_KAFKA_BOOTSTRAP_SERVERS}}" >>.env.prod + echo "KAFKA_CONSUMER_GROUP_ID=${{secrets.PROD_KAFKA_CONSUMER_GROUP_ID}}" >>.env.prod echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin diff --git a/docker-compose-local.yaml b/docker-compose-local.yaml index 0c8f15b0..45d7b4f7 100644 --- a/docker-compose-local.yaml +++ b/docker-compose-local.yaml @@ -26,6 +26,8 @@ services: - DB_PASSWORD=${DB_PASSWORD} - REDIS_PORT=6379 - REDIS_HOST=redis + - KAFKA_BOOTSTRAP_SERVERS=kafka:29092 + - KAFKA_CONSUMER_GROUP_ID=devnogi-community-group - JWT_SECRET_KEY=${JWT_SECRET_KEY} depends_on: mysql: @@ -102,6 +104,33 @@ services: interval: 5s timeout: 3s retries: 5 + + kafka: + image: confluentinc/cp-kafka:7.6.0 + container_name: devnogi-community-kafka + ports: + - "9092:9092" + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_BROKER: kafka:29092 + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:29093,PLAINTEXT_HOST://0.0.0.0:9092 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093 + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk + networks: + - my-network + healthcheck: + test: [ "CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list" ] + interval: 10s + timeout: 5s + retries: 5 + networks: my-network: driver: bridge \ No newline at end of file diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index bb6f3c57..e229d05e 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -24,7 +24,8 @@ services: - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY} #ID - MINIO_SECRET_KEY=${MINIO_SECRET_KEY} #PASSWORD - MINIO_BUCKET=${MINIO_BUCKET} - + - KAFKA_BOOTSTRAP_SERVERS=${PROD_KAFKA_BOOTSTRAP_SERVERS} + - KAFKA_CONSUMER_GROUP_ID=${PROD_KAFKA_CONSUMER_GROUP_ID} - JWT_SECRET_KEY=${JWT_SECRET_KEY} - TZ=Asia/Seoul diff --git a/src/main/java/until/the/eternity/dcs/common/kafka/KafkaConsumerService.java b/src/main/java/until/the/eternity/dcs/common/kafka/KafkaConsumerService.java new file mode 100644 index 00000000..bacc584d --- /dev/null +++ b/src/main/java/until/the/eternity/dcs/common/kafka/KafkaConsumerService.java @@ -0,0 +1,16 @@ +package until.the.eternity.dcs.common.kafka; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class KafkaConsumerService { + @KafkaListener(topics = "test-topic", groupId = "devnogi-community-group") + public void consume(TestDTO message) { + log.info("컨수머 테스트 시작"); + log.info("🎉 Kafka로부터 받은 메시지: {}", message); + log.info("컨수머 종료"); + } +} diff --git a/src/main/java/until/the/eternity/dcs/common/kafka/KafkaTestController.java b/src/main/java/until/the/eternity/dcs/common/kafka/KafkaTestController.java new file mode 100644 index 00000000..17ae1a20 --- /dev/null +++ b/src/main/java/until/the/eternity/dcs/common/kafka/KafkaTestController.java @@ -0,0 +1,30 @@ +package until.the.eternity.dcs.common.kafka; + +import static org.springframework.http.HttpStatus.NO_CONTENT; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/kafka") +@Slf4j +public class KafkaTestController { + private final KafkaTemplate kafkaTemplate; + + public KafkaTestController(KafkaTemplate kafkaTemplate) { + this.kafkaTemplate = kafkaTemplate; + } + + @PostMapping("/test/send") + public ResponseEntity sendMessage(@RequestBody TestDTO msg) { + log.info("kafka controller 테스트 시작: {}", msg); + kafkaTemplate.send("test-topic", msg); + log.info("kafka controller 테스트 끝"); + return ResponseEntity.status(NO_CONTENT).build(); + } +} diff --git a/src/main/java/until/the/eternity/dcs/common/kafka/TestDTO.java b/src/main/java/until/the/eternity/dcs/common/kafka/TestDTO.java new file mode 100644 index 00000000..49550fb8 --- /dev/null +++ b/src/main/java/until/the/eternity/dcs/common/kafka/TestDTO.java @@ -0,0 +1,3 @@ +package until.the.eternity.dcs.common.kafka; + +public record TestDTO(String title, String content) {} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bf6d7303..14905cfb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -113,6 +113,20 @@ spring: config: activate: on-profile: local + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:kafka:29092} + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + consumer: + group-id: devnogi-community-group + auto-offset-reset: latest + + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer + properties: + spring.json.trusted.packages: "*" decorator: datasource: @@ -124,6 +138,7 @@ logging: until.the.eternity: DEBUG until.the.eternity.dcs.common.filter: DEBUG + --- # 운영 환경 spring: @@ -137,6 +152,17 @@ spring: password: ${REDIS_PASSWORD:} ssl: enabled: false + kafka: + bootstrap-servers: ${PROD_KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + consumer: + group-id: ${PROD_KAFKA_CONSUMER_GROUP_ID:devnogi-community-group} + auto-offset-reset: latest + + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer + properties: + spring.json.trusted.packages: "*" springdoc: swagger-ui: @@ -148,6 +174,8 @@ minio: secret-key: ${MINIO_SECRET_KEY:minioadmin} bucket: ${MINIO_BUCKET:dcs-local-bucket} + + decorator: datasource: p6spy: @@ -156,6 +184,7 @@ decorator: logging: level: until.the.eternity: INFO + org.springframework.kafka: DEBUG --- # Docker 환경 @@ -168,8 +197,20 @@ spring: host: ${REDIS_HOST:devnogi-community-redis} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} - + kafka: + bootstrap-servers: kafka:29092 + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + consumer: + group-id: devnogi-community-group + auto-offset-reset: latest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer + properties: + spring.json.trusted.packages: "*" logging: level: root: INFO - until.the.eternity: DEBUG \ No newline at end of file + until.the.eternity: DEBUG + org.springframework.kafka: DEBUG \ No newline at end of file