728x90

서비스를 출시한 지 두 달이 되어갈 무렵, 여러 건의 견적 문의가 들어오면서 실제 매출로 이어지기 시작했다. 이 과정을 통해 기존 B2C를 넘어 B2B까지 확장할 수 있는 기회도 엿볼 수 있었다.

 

무엇보다도 고객들로부터 직접 견적 문의가 들어오고, 실제 시공까지 연결되어 수익이 발생했다는 점에서 큰 보람을 느꼈다. 내가 만든 서비스가 고객의 니즈를 충족시켰다는 사실이 뿌듯하게 다가왔다. 예전에 배달의민족 김범준 대표가 “개발자는 코딩하는 사람이 아니라 문제를 해결하는 사람이다”라고 말했던 것이 문득 떠올랐다. 그 말을 처음 들었을 땐 단순히 좋은 말이라 생각했지만, 지금은 그 의미를 몸소 체감하고 있다.

 

샷시 가격을 측정하는 데 불편함을 겪던 고객들, 그리고 그 가격이 합리적인지 판단할 기준조차 없었던 상황을 ‘호빵’이 해결했다. 그 결과, 현재까지 약 250명의 고객이 300건 이상의 견적을 받아보았다. 이 수치는 고객의 실질적인 문제를 해결했기 때문에 가능한 성과라고 생각한다. 그리고 그런 문제 해결이 결국 수익으로 이어졌기에, “해결하는 사람”이라는 말이 더욱 깊게 와 닿았다.

 

최근에는 샷시 공장을 운영하는 한 기업으로부터 연락도 받았다. 그 회사는 이전에 SI 업체에 호빵과 유사한 견적 시스템을 의뢰했지만, 가격 오차 범위가 너무 커서 결국 프로젝트를 포기한 경험이 있었다고 한다. 그런데 이번엔 내 서비스를 보고 직접 개발을 맡기고 싶다는 제안을 해왔다. 아직 협의 중이지만, 이 일을 통해 공장 내부에서 사용할 수 있는 수요가 분명히 존재한다는 사실도 확인할 수 있었다.

 

이런 흐름 속에서 ‘호빵 비즈니스(가칭)’라는 이름으로 기업용 서비스를 별도로 기획해볼 계획이다. 서비스의 가능성이 B2C를 넘어 B2B까지 확장될 수 있다는 점에서 새로운 도전이자 기회라고 생각한다.

'난중(개발)일기 > [프로젝트] 호빵' 카테고리의 다른 글

서비스 시작 후 1달차 회고  (1) 2025.03.23
출시 후 2주차 회고  (1) 2025.03.03
728x90

org.apache.kafka.connect.errors.ConnectException: Error configuring an instance of KafkaSchemaHistory; check the logs for details\n\tat io.debezium.storage.kafka.history.KafkaSchemaHistory.configure(KafkaSchemaHistory.java:208)\n\tat io.debezium.relational.HistorizedRelationalDatabaseConnectorConfig.getSchemaHistory(HistorizedRelationalDatabaseConnectorConfig.java:137)\n\tat io.debezium.relational.HistorizedRelationalDatabaseSchema.<init>(HistorizedRelationalDatabaseSchema.java:50)\n\tat io.debezium.connector.binlog.BinlogDatabaseSchema.<init>(BinlogDatabaseSchema.java:79)\n\tat io.debezium.connector.mysql.MySqlDatabaseSchema.<init>(MySqlDatabaseSchema.java:41)\n\tat io.debezium.connector.mysql.MySqlConnectorTask.start(MySqlConnectorTask.java:99)\n\tat io.debezium.connector.common.BaseSourceTask.start(BaseSourceTask.java:251)\n\tat org.apache.kafka.connect.runtime.AbstractWorkerSourceTask.initializeAndStart(AbstractWorkerSourceTask.java:278)\n\tat org.apache.kafka.connect.runtime.WorkerTask.doStart(WorkerTask.java:175)\n\tat org.apache.kafka.connect.runtime.WorkerTask.doRun(WorkerTask.java:224)\n\tat org.apache.kafka.connect.runtime.WorkerTask.run(WorkerTask.java:280)\n\tat org.apache.kafka.connect.runtime.AbstractWorkerSourceTask.run(AbstractWorkerSourceTask.java:78)\n\tat org.apache.kafka.connect.runtime.isolation.Plugins.lambda$withClassLoader$1(Plugins.java:237)\n\tat java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)\n\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)\n\tat java.base/java.lang.Thread.run(Thread.java:1583)\

 


문제의 Debezium-DB 연결 명령

curl -X POST http://localhost:8083/connectors \

-H "Content-Type: application/json" \

-d '{

  "name": "{커넥터_이름}",

  "config": {

    "connector.class": "io.debezium.connector.mysql.MySqlConnector",

    "database.hostname": "{MySQL_호스트_이름_or_컨테이너_이름}",

    "database.port": "{MySQL_포트}",

    "database.user": "{MySQL_사용자명}",

    "database.password": "{MySQL_비밀번호}",

    "database.server.id": "{MySQL_복제_서버_ID(고유숫자)}",

    "database.include.list": "{감지할_DB명}",

    "topic.prefix": "{Kafka_토픽_프리픽스}",     

    "database.server.name": "{Kafka_토픽_서버명}",

 

    "database.history.kafka.bootstrap.servers": "{Kafka_호스트:포트}",

    "database.history.kafka.topic": "{Kafka_스키마_히스토리_토픽명}",

    

    "include.schema.changes": "true"

  }

}'

 

여기에서 에러가 났다.

이유는 Debezium 버전문제였다. 1.x 버전은 Kafka 스키마 히스토리 토픽명의 key값으로 database.history.kafka.topic 으로 해야하고 2.x 버전은 schema.history.internal.kafka.topic 으로 해야했다. 그래서 

 

curl -X POST http://localhost:8083/connectors \

-H "Content-Type: application/json" \

-d '{

  "name": "{커넥터_이름}",

  "config": {

    "connector.class": "io.debezium.connector.mysql.MySqlConnector",

    "database.hostname": "{MySQL_호스트_이름_or_컨테이너_이름}",

    "database.port": "{MySQL_포트}",

    "database.user": "{MySQL_사용자명}",

    "database.password": "{MySQL_비밀번호}",

    "database.server.id": "{MySQL_복제_서버_ID(고유숫자)}",

    "database.include.list": "{감지할_DB명}",

    "topic.prefix": "{Kafka_토픽_프리픽스}",     

    "database.server.name": "{Kafka_토픽_서버명}",

 

    "database.history.kafka.bootstrap.servers": "{Kafka_호스트:포트}",

    "schema.history.internal.kafka.topic": "{Kafka_스키마_히스토리_토픽명}",

    

    "include.schema.changes": "true"

  }

}'

 

이렇게 하니까 잘 된다.

728x90

한 달 만에 195명, 호빵 유저를 모으며 느낀 마케팅의 힘

 

호빵 서비스를 본격적으로 알리기 시작한 지 어느덧 한 달이 넘었습니다. 그 사이 약 180명의 유저가 가입했고, 그 과정을 통해 가장 크게 느낀 점은 “마케팅은 사업의 절반 이상”, 어쩌면 80% 이상일 수도 있다는 사실이다.

 

단순히 좋은 서비스를 만드는 것만으로는 충분하지 않았습니다. 어떻게 알릴지, 어디에 노출할지, 어떤 메시지로 전달할지가 오히려 더 중요하다는 것을 매일 실감하고 있다.

 

 

데이터 기반 마케팅의 위력

 

마케팅 전략을 세우는 데 있어 통계 데이터의 힘은 정말 강력했다.

총 가입자 수

등록된 견적 수

앱 유입 비율

일별 유저 유입/탈퇴/견적 등록 추이

지역별 견적 등록 수

유입 경로별 클릭 수

 

이 모든 데이터를 시각화하여 매일 확인하고 분석했다.

 

예를 들어, 지역별 견적 등록 수를 분석해보니 서울/경기권의 수요가 압도적으로 높다는 것을 확인할 수 있었고, 이를 기반으로 인스타그램 광고의 타겟 지역을 정했다.

 

또한 일별 유저 유입 그래프를 통해 신규 유저가 주말에 더 많이 유입된다는 사실을 파악했고, 그에 맞춰 광고 예산을 주말에 집중적으로 투입하는 전략을 실행했습니다. 작은 실험이었지만, 분명한 효과를 체감할 수 있었다.

 

 

고객의 ‘진짜 니즈’에 다가가기

 

마케팅을 하며 또 한 가지 중요한 것을 배웠다.

바로 잠재고객들이 무엇을 원하는지를 명확히 파악해야 한다는 것다.

 

서비스에 대한 메시지를 조금씩 바꿔가며 실험해봤습니다. 블로그, 카페, SNS 등에 홍보 글을 올릴 때마다 서비스 URL에 쿼리 파라미터를 다르게 설정해 경로별 클릭 수를 추적했다.

 

그 결과, 유저들이 가장 관심을 가지는 포인트는 단연 “가격”이었다.

 

예를 들어 인스타그램 광고에서 단순한 이미지보다, 정확한 수치가 명시된 이미지를 올렸을 때 클릭율이 훨씬 높았다.

 

단순 텍스트 설명 이미지 (클릭률 2.2%)

 

 

정확한 가격 수치 포함 이미지 (클릭률 7.8%)

 

무려 5.6%포인트 차이였다.

이 작은 차이가 실제 유저 유입에는 큰 차이를 만들어냈다.

 

 

결과

 

지금도 호빵의 마케팅은 실험 중이다.

어떤 문구가 사람들의 마음을 움직이는지, 어떤 플랫폼에서 전환율이 높은지,

그리고 결국 어떤 정보가 고객에게 ‘신뢰’로 다가가는지.

 

이 모든 것을 고민하고 실험하며, “가장 정확하고, 가장 합리적인 창호 견적 플랫폼” 으로 자리잡기 위한 여정을 계속하고 있다.

 

앞으로 구글, 네이버 등 광고를 진행 하여 고객님들의 반응을 살펴 볼 예정이다.

 

추가적으로 할인 이벤트를 할 예정인데 문구를 어떻게 해야 할지 광고를 해서 반응을 살펴 봐야할 것 같다.

 

728x90

개인프로젝트를 앱스토어, 플레이스토어에 둘 다 출시 했다.

 

앱 명은 인테리어를 하면서 사람들이 소위 호구를 많이 당하는데, 구를 명으로 만들고자 호빵으로 지었다.

 


기획부터 프론트엔드(웹, 앱), 백엔드, 인프라까지 혼자 맡아서 하다 보니 퇴근 후 작업이 매우 힘들었지만, 값진 경험이었다. 게다가 실제 유저들까지 유입되어 보람을 느꼈다. 현재 유저는 총 81명으로 (가족과 지인을 제외한 수치) 특별히 돈을 안 들이고 블로그 글 4개로 얻은 성과인 점을 감안하면 개인적으로 매우 의미 있는 숫자라고 생각한다.

혼자서 많은걸 하다 보니 UI/UX에 신경을 충분히 쓰지 못한 것 같다. 현업에서는 백엔드 개발을 하고 있고, 프론트엔드는 학교 과제나 개인 프로젝트를 통해 일부 경험해 본 것이 전부라 어려움이 있었다. 또한, 처음 해본 앱 개발이 가장 큰 난관이었다.

실제 유저가 유입되었으며, 전체 서비스 이용률은 약 30~40%였고, 회원가입 과정에서의 이탈률은 약 20%였다. 이를 기반으로 데이터를 분석해 보았다.

1. 유저의 UI에 대한 불신
앱이 렌더링되는 디바이스 화면에 맞게 CSS가 제대로 적용되지 않았다. (출시 후 조금씩 조정하자는 생각으로 미뤄두었다. 사실상 선택과 집중을 한 것이었다고 생각한다. 한정된 시간 속에서 익숙한 백엔드 쪽에 더 공을 들인 것이 가장 큰 이유였다.)

2. 불편하고 친절하지 않은 UX로 인한 불신
예를 들어, 최초 회원가입 후 유저의 휴대폰 인증 과정에서 인증번호 실패 시의 대처 전략이 미흡했다. 렌더링되는 디바이스에 맞게 오류 메시지가 보여야 하는데, 너비가 좁은 디바이스에서는 해당 메시지가 보이지 않아 유저가 당황하고 이탈한 것으로 판단했다.

 

 

결론

위 문제점들을 개선한 결과, 유저 이탈률이 감소하고 전체 서비스 이용률이 증가하는 긍정적인 변화를 경험했다.

728x90

RN 으로 배포 후 카카오로그인을 했는데 자꾸 안 됐다.

 

로그를 보니 해시키값이 문제인 것 같았다.

 

그래서 배포할 때 사용한 배포용 keystore 해시키를 추출해서 사용 했는데 안 됐다. (다음과 같이)

keytool -exportcert -alias androiddebugkey -keystore ~/.android/release.keystore -storepass 123456 -keypass 123456 | openssl sha1 -binary | openssl base64

 

이런식으로 해도 안됐다.

 

그래서 GooglePlayConsole > 앱 무결성 > Play 앱 서명 >  앱 서명 키인증서 > SHA-1 인증서 지문 값을 복사 해서 Base64로 컨버팅 한 후 등록 하니까 됐다. 

 

사용 한 컨버터는 다음과 같다. https://tomeko.net/online_tools/hex_to_base64.php?lang=en

728x90

서버 로그를 그라파나에 보여지게 하려는데, 에러가 나를 몇시간 동안 괴롭혔다.

 

해당 에러를 해석 해보자면 연결은 됐지만 라벨을 받지 못했으니 Loki, Promtail 설정을 잘해봐라. 라는 뜻으로 이해했다.

 

처음에는 Docker compose 파일에 다음과 같이 Promtail 컨테이너를 세팅했다.

 

 

문제는 Promtail 설정이 담긴 promtail-config.yml 파일명이었다. 하이픈을 제거해서 promtail.yml로 변경하니 됐다.

 

 

728x90

다음과 같은 에러가 나와 고생좀 했다.

github.com/prometheus/prometheus/promql.NewActiveQueryTracker({0x3053abc, 0x5}, 0x14, {0x39f12e0, 0xc000099400})
	/app/promql/query_logger.go:121 +0x3cd
main.main()
	/app/cmd/prometheus/main.go:597 +0x6713
ts=2024-10-13T13:46:01.358Z caller=main.go:491 level=info msg="No time or size retention was set so using the default time retention" duration=15d
ts=2024-10-13T13:46:01.358Z caller=main.go:535 level=info msg="Starting Prometheus Server" mode=server version="(version=2.37.6, branch=HEAD, revision=8ade24a23af6be0f35414d6e8ce09598446c29a2)"
ts=2024-10-13T13:46:01.358Z caller=main.go:540 level=info build_context="(go=go1.19.6, user=root@5f96027a7c3e, date=20230220-09:36:40)"
ts=2024-10-13T13:46:01.358Z caller=main.go:541 level=info host_details="(Linux 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr  4 14:39:20 UTC 2 x86_64 40ea8565bbf0 (none))"
ts=2024-10-13T13:46:01.358Z caller=main.go:542 level=info fd_limits="(soft=1048576, hard=1048576)"
ts=2024-10-13T13:46:01.359Z caller=main.go:543 level=info vm_limits="(soft=unlimited, hard=unlimited)"
ts=2024-10-13T13:46:01.359Z caller=query_logger.go:113 level=error component=activeQueryTracker msg="Failed to create directory for logging active queries"
ts=2024-10-13T13:46:01.359Z caller=query_logger.go:91 level=error component=activeQueryTracker msg="Error opening query log file" file=/prometheus/data/queries.active err="open data/queries.active: no such file or directory"
panic: Unable to create mmap-ed active query log

goroutine 1 [running]:
github.com/prometheus/prometheus/promql.NewActiveQueryTracker({0x3053abc, 0x5}, 0x14, {0x39f12e0, 0xc0004b9540})
	/app/promql/query_logger.go:121 +0x3cd
main.main()
	/app/cmd/prometheus/main.go:597 +0x6713

 

Prometheus가 /prometheus/data/queries.active 파일을 생성하려고 할 때 권한이 부족하다는 문제인 것 같았다. 그래서 찾다가 다음링크를 참고해서 해결했다.

 

https://github.com/prometheus/prometheus/issues/5976#issuecomment-535455952

 

err="open /prometheus/queries.active: permission denied" · Issue #5976 · prometheus/prometheus

Proposal Use case. Why is this important? centos7 + docker-ce + prometheus Bug Report no change docker default: Docker Root Dir i can docker run prometheus right when i change Docker Root Dir eg:Do...

github.com

 

 

이 설정은 호스트의 파일 시스템과 컨테이너 간 권한 문제를 해결하는 방법이다. 특히 공유된 볼륨에서 컨테이너 내부에서 파일을 작성하고 수정할 때 발생할 수 있는 충돌을 방지한다. UID 1000:1000로 실행하면 호스트의 동일한 사용자 권한을 가진 것처럼 컨테이너가 동작하게 되어, 파일 시스템에 접근할 때 권한 문제가 없어진다.

728x90

람다 SnapStart 를 적용하며 

람다에 자바로 된 함수를 띄었다. 자바 파일은 아무래도 node.js에 비해 사이즈가 컸다. 
기존에 회사 넥서스에 올라가 있는 라이브러리를 사용하려다 보니 자바로 어쩔 수 없이 해야 했던 상황이다. (다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다)
그래서 50mb 정도 되는 자바 코드를 그대로 올려서 cold start 로 실행 시켰다.
그러다 보니. 람다 자체내에서 코드를 다운로드 하고, 컨테이너를 띄우고, 전역 코드를 실행 후 내부 함수를 실행하는 순서 까지가 일반적인 방식으로 40000~50000ms(4~50s) 정도가 걸렸다.

처음에 ColdStart로 40초 정도 걸리니 방법을 모색 해보았다. 처음에는 다음과 같은 방법을 찾았다.
- 타임아웃 늘리기
- node.js 로 다시 구현
- 프로비저닝 동시성 구성
- 해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

 

타임아웃 늘리기

근본적인 해결책은 아닌 것 같았다. 해당 함수가 띄어질 때 까지 40~50초 가량 걸리는데, 빌드될 때 까지 기다리고 처리될 때 까지 함수가 내려지지 않을 정도의 시간을 주는 것이었다.


node.js 로 다시 구현

위에서 말했듯이 다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다. 그리고 해당 라이브러리에 은근 로직들이 많아 새로 다른 언어로 로직을 만드는데 리스크를 안아야 하는점도 있었다.


프로비저닝 동시성 구성

항상 함수를 띄어 놓으면 그만큼 과금이 된다. 하루에 특정 시간대에 한 두번 도는 함수인데, 함수가 무거워서 컨테이너가 올라갈 때 시간이 많이 걸린다는 이유만으로 해당 금액을 지불하기엔 돈이 아까웠다.


해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

해당 로직을 처리 해야 하는 예상 시간을 추측 해서 5분 마다 헬스체크 하면서 warm up 시켜놓는 방식도 괜찮았지만, 근본적인 해결책이라고는 볼 수 없었다. 헬스체크를 보내야 하는데, 이를 보내는 주체에 의존하는 모양이 되기 때문에, 주체에 문제가 생기면 warm up이 안되기 때문이다.


지금까지의 고민을 SnapStart이 모든것을 해결해 주었다.

 

SnapStart 란 해당 버전의 빌드 파일의 스냅샷을 따서 캐싱 하고(스냅샷은 Amazon EBS 에 저장), Cold Start를 할 때 이를 활용하여 실행하는 기능이다. 그래서 람다 컨테이너를 올릴 때 아무리 큰 함수라도 빠르게 올릴 수 있다.

 

스냅샷은 구체적으로 함수 코드가 로드되고 초기화된 상태를 저장한다.

 

메모리상태
- Java 런타임에 의해 로드되고 초기화된 모든 클래스
- 정적 변수
- 초기화된 객체
- JIT 컴파일된 코드
- 의존성
- 캐시된 데이터
- 연결 풀
- 스레드 상태


디스크 상태
- 함수 코드와 의존성 라이브러리들이 로드된 상태
- 런타임 환경에 의해 생성된 파일 시스템 구조


그러나 SnapStart에 제약사항은 존재한다.


런타임 제약사항

- 자바 전용이며 Java 11, 17을 지원.
- x86 아키텍처만 동작.
- 프로비저닝된 동시성 불가.
- EFS(Amazon Elastic File System) 연동 불가
- 512MB 이상의 임시 스토리지 불가
- 배포시간 증가 - 개발 생산성에 영향을 줌 (배포 파이프라인에 영향)
- 고유성에 영향을 줌 - 글로벌 변수에 난수 생성은 스냅샷에 포함되어 동일한 값이 사용되게 되기 때문이다. (함수에서 생성하거나 hook을 통해 처리 해야 함)

 

참고자료

 

728x90

master - slave 구조로 변경하려고 했다.

 

https://velog.io/@ch4570/MySQL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%82%B0-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-Master-Slave-%EC%9D%B4%EC%A4%91%ED%99%94%EB%A5%BC-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8B%A4MySQL-Replication-%EC%84%A4%EC%A0%95%EA%B3%BC-%EA%B5%AC%EC%84%B1

 

[Pet-Hub] MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구성(MySQL Replication 설정)

지금껏 프로젝트를 진행하면서 대부분 RDS 서비스를 이용해 한개의 데이터베이스만 사용해왔습니다.이번 프로젝트의 중점 목표 중 하나인 고가용성의 설계와 확장성 있는 구조를 가진 서버를

velog.io

https://velog.io/@zvyg1023/mysql-master-slave

 

[MySQL] Master-Slave 구축

MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구축 이중화를 하는 이유 데이터베이스의 트랜잭션을 readOnly로 셋팅할경우 성능이 상승하는 이점을 누릴 수 있습니다. 데이터베이스에서 일어나

velog.io

 

이 글들을 참고 해서 계속 시도를 해보았는데 조금 어려움을 겪었다.

 

하라는 대로 했는데 replication이 안됐다. 

 

SHOW SLAVE STATUS\G 명령을 실행하여 레플리케이션 상태를 확인 하는데, Slave_IO_Running Slave_SQL_Running이 모두 "Yes"였다. 잘 설정 되었다고 생각 했는데 안되는 것이었다.

 

그래서 생각 해보니 이런 현상이 발생한 주요 원인은 레플리케이션 설정 과정에서 초기 데이터 동기화가 제대로 이루어지지 않았기 때문일 가능성이 높다고 생각했다.

  1. 초기 데이터 불일치:
    레플리케이션을 설정할 때 마스터의 기존 데이터를 슬레이브로 복사하지 않았을 가능성.
  2. 바이너리 로그 포지션 오류:
    슬레이브가 마스터의 바이너리 로그에서 잘못된 위치부터 복제를함. 이 경우, 새로운 변경사항만 복제되고 이전 데이터는 누락 될 가능성이 있음.

그래서 master를 덤프하고 slave에 덮어 씌워 해결 했다.

 

1. 마스터 데이터베이스에서 덤프를 한다.

docker exec -it hoppang_database_master bash

 

mysqldump -u root -p --all-databases --master-data=2 --single-transaction --flush-logs --hex-blob > /master_data.sql

 

그러면 덤프 파일이 생긴 것을 확인할 수 있다.

 

덤프 파일을 호스트서버로 이동 시킨 후

docker cp hoppang_database_master:/master_data.sql /home/twj/master_data.sql

 

호스서버에서 슬레이브 데이터베이스로 이동시킨다.

docker cp ./master_data.sql hoppang_database_slave:/tmp/master_data.sql

 

슬레이브 데이터베이스로 이동 후.

docker exec -it hoppang_database_slave bash

 

덤프를 덮어 씌운다. ( 여기에서 hoppang은 스키마 이름이다. ) 저 명령어를 입력 하면 root로 가는 패스워드를 입력하면 성공한다.

mysql -u root -p hoppang < /tmp/master_data.sql

 

 

이 이후에 성공적으로 replication이 이루어졌다.

728x90

 

들어가며

프록시 (Proxy) 란 한 마디로 대리 기사라고 표현할 수 있다. 프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 점속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.
 
 
 

Forward Proxy

클라이언트가 프록시 서버에 요청을 보내고, 프록시 서버가 요청을 받아 실제 서버로 전달한다.
 

Reverse Proxy

클라이언트가 실제 서버(또는 서비스)의 주소를 모르고, 대신 프록시 서버의 주소로 요청을 보내면, 프록시 서버가 요청을 적절한 백엔드 서버로 전달한다.
 
 
 

AS-IS

 
변경 전 구조는 위 그림과 같다. 공유기에서 바로 해당하는 프록시가 있는 포트로 포트포워딩 해주는 구조였다. 
 
해당 구조는 작업하기에 편했지만, 서버의 포트를 많이 열어 주어야 하기 때문에 생길 보안이슈로 부담스러웠다.
 

  • 공격 표면 증가: 여러 포트를 열어 두면 그만큼 공격자가 침입할 수 있는 경로가 많아져서, 해커가 특정 포트를 타겟으로 하는 공격(예: 포트 스캐닝, DoS 공격)을 시도할 가능성이 높아짐.

  • 취약점 노출: 열려 있는 포트가 사용하는 서비스나 소프트웨어에 보안 취약점이 있다면, 이를 통해 쉽게 침입할 수 있음. 특히 포트가 열려 있으면 버전 정보나 서비스 정보가 노출될 가능성이 있어 공격에 취약함.

 
뿐만 아니라 추가적인 이슈로는 유지보수, 서버 리소스, 스케일링 복잡성, 인증서 관리의 복잡성 등의 추가적인 이슈가 예상됐다.
 
첫 번째로, 유지보수성에 단점이 있다. 각각의 프로젝트에서 docker-compose 파일로 관리를 해야 하고 nginx.conf 파일도 따로 관리를 하는 부분이 번거로웠다. 설정 변경할 때 또한 일관성을 유지 하기에 어려움이 있었다. 각 프로젝트에서 일일히 수정 작업이 들어가니 애를 먹었다. 

그리고 두 번째로, 이 구조는 네트워크 오버헤드 발생할 수 있을 것 같다. 각 서버 마다 Nginx를 통해 통신을 해야 하는데 이는 불필요한 통신이 많아지기 때문에 대용량 트래픽 처리에 좋지 않아 보인다.

세 번째로, 서버 리소스 낭비이다. Nginx 인스턴스가 WAS마다 별도로 존재하면 서버 자원을 많이 소비한다.
 
네 번째로, 스케일링 복잡성이다. 이는 유지보수성이랑도 연관 되는데 시스템 확장 시 각 Nginx를 별도로 조정해야 하므로, 스케일링이 번거롭고 관리 포인트가 늘어날 수 있다.
 
다섯 번째로, 인증서 관리의 복잡성이다. 각각의 Nginx에 적용을 해야 하는 공수가 있다. 공유 스토리지를 사용하는 방법도 있는데 덕지덕지 붙여지는 느낌이라 꺼려졌다.

그래서 다음과 같이 단일 Nginx로 통합되는 구조로 변경 해보았다.
 
 
 

TO-BE

 
 
이렇게 변환하니 많은 변화가 생겼다.
 
CI/CD의 변화, 단일 프록시로 통합, 설정 중앙화, 유연한 스케일링, 유지보수성 등 좋아짐.
 
기존 CI/CD는 각 애플리케이션 저장소에서 각각 이미지화 해서 Nginx와 함께 서버가 띄어졌다면, 변경 된 후 하나의 docker compose 파일로 관리 되어서 배포 될 때 마다 해당 파일을 참조하여 빌드할 수 있게 됐다.
 
하나의 docker-compose 파일로 관리 하게 되어서 서버 구조를 한 눈에 볼 수 있어서 더 편했다. 더 나아가 각각 컨테이너가 논리적으로 하나의 network으로 묶인 상태이면 각각이 내부적으로 container name으로 호출할 수 있는 구조가 되어서 외부와 단절시킬 수 있는 큰 장점이 있다고 생각 되었다. 그래서 보안적으로도 큰 이점이 있다고 생각했다. (도커 내부적으로 DNS를 운영 하기 때문에 가능한 부분이다) 그리고 단일  docker-compose 파일로 관리 하는 것은 scale up/out 다 용이했다.
 
단일 프록시로 통합 되어서 서버 리소스를 절약할 수 있었고, 분산 된 설정을 한 곳에서 유지보수 할 수 있어서 훨 편했다.
 

+ Recent posts