게시글
질문&답변
goopang-target-group 에서 unhealthy
네 맞습니다.하나의 인스턴스에서 유저 데이터 스크립트가 초기에 함번 실행되고, 중단하고 다시 시작할땐 스크립트기 실행되지 않습니다.현재 구조로 답변 드리면 계속 접속해서 실행시켜줘야하는게 맞습니다.하지만 이후 강의에서 배우게 되는 시작템플릿과 오토스케일링 그룹을 도입하게 되면 이런 부분은 간단하게 해결되니 이후 강의도 수강해주시면 도움 되실거에요!넵 이해했습니다! 덕분에 찝찝했던 부분 없이 속 시원하게 해결되었습니다! 감사합니다!!
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 답변해주신 내용 확인했습니다!그렇다면, 초기에 private-ec2-instance 를 처음 실행할 때가 아닌, Stop Instance 로 중단했다가, Start Instance 로 인스턴스를 재시작하는 경우에는 User Data 의 스크립트가 실행되지 않는다는 말씀이시죠? 그렇다면 인스턴스 실행을 중단(Stop Instance)을 했다가 재실행(Start Instance)할 때 마다 User Data 의 스크립트를 실행하는 방법은 없을까요?매번 private-ec2-instance 에 접속해서 직접 스크립트를 실행해서 테스트를 진행해야 할까요?
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 빠른 답변 항상 감사드립니다.말씀해주신 스크립트 내용을 private-ec2-instacne 의 User Data 내용으로 변경 후에는 역시나 Health checks failed 하였으나,private-ec2-instance 에 EC2 Instance Connect Endpoint 를 이용하여 직접 접속한 후에 aws-operation-prac 으로 폴더 이동 후에 아래 명령어를 직접 실행했을 때는 health check 성공한 것으로 확인됩니다. ./gradlew build sudo java -jar build/libs/aws-msa-monolithic-prac-0.1.jar 뭔가 ec2 인스턴스에서 sudo java -jar 로 애플리케이션 실행 후에 뭔가 문제가 있는 걸까요? private-ec2-instance 에 직접 접속 후에 ls-al 명령어로 파일 확인 시에 aws-operation-prac 프로젝트도 정상적으로 git clone 되어 있는 것 확인했습니다.
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
우선 target group의 health check settings에 interval 시간이나 Unhealthy threshold 수를 늘려보면 좋을것같습니다.interval - 30초 이상Unhealthy threshold - 4회 이상이렇게 넉넉하게 설정해서 테스트하고, 테스트 한번 부탁드릴게요!넵 답변 감사합니다 강사님. 말씀해주신 것 처럼 Unhealthy threshold - 5회, interval - 30초 | Unhealthy threshold - 7회, interval - 60초 로 설정해보았는데, 2번 다 여전히 health checks failed 로 확인됩니다.. 추가적으로 확인해볼 부분이 있을까요..? (사진) (사진)
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 빠른 답변 항상 감사드립니다. 지금으로 의심되는 부분은 보안그룹 설정 정도 될것같습니다.alb에서 ec2 인스턴스로 가는 트래픽에 대한 보안그룹 설정 한번 확인 부탁드릴게요!정상적으로 설정되어있다면, 인스턴스가 생성되고, 애플리케이션이 실행된 상태에서 인스턴스에 접속하여 curl로 health_check 요청이 정상적으로 되는지 그리고 target group에 설정된 health check 설정도 확인해보면 좋을것같습니다! 말씀해주신 부분들 전부 확인해보았는데, 문제가 없어 보여서 다시 질문 드립니다..우선, goopang-alb-sg 의 inbound-rules 에서 http 요청 받을 수 있도록 설정 되어 있고, goopang-private-ec2-sg 의 inbound-rules 에는 alb 와 bastion-host sg 들 요청 받을 수 있도록 설정되어 있는 것 확인했습니다. target-group 의 Health check settings 도 아래 이미지와 같이 /health_check 경로 설정 등 강의 설명과 같이 설정되어 있는 것 확인했습니다.bastion-host 인스턴스를 이용하여 private-ec2-instance 접속 후에 curl -XGET 'localhost/health_check' 로 요청 정상 작동 확인 시에는 아래의 코드 블럭 내용과 같이 server 에 접속 실패하였다고 나옵니다.private-ec2-instance에 EC2 Instance Connect Endpoint 를 이용하여 직접 접속 후 같은 요청을 보내보아도 아래 코드 블럭과 같은 에러(Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server)가 반환됩니다.. private-routing-table 에도 nat-instance 설정되어 있는 것 확인했습니다. User Data 스크립트 log 확인(cat /var/log/cloud-init-output.log)은 이전 답글에 보낸 것과 같이 AwsMsaMonolithicPracApplicationKt 서버가 제대로 떴다가 다시 shutdown 되는 것으로 보입니다.. 혹시 제가 추가적으로 확인해볼만한 것이 있을까요..?(위에 글로 설명한 부분들 확인하기 쉽게 아래의 로그들과 이미지들을 첨부했습니다.)[ec2-user@ip-10-0-1-10 ~]$ curl -XGET 'localhost/health_check' curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server 2024-10-24T16:36:51.158Z INFO 25901 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 80 (http) with context path '' 2024-10-24T16:36:51.182Z INFO 25901 --- [ main] .p.m.a.AwsMsaMonolithicPracApplicationKt : Started AwsMsaMonolithicPracApplicationKt in 7.333 seconds (process running for 8.101) Hibernate: select count(*) from users u1_0 Hibernate: select count(*) from products p1_0 Hibernate: select count(*) from cart_items c1_0 Hibernate: insert into users (email,name,password,id) values (?,?,?,default) Hibernate: insert into users (email,name,password,id) values (?,?,?,default) Hibernate: insert into products (description,name,price,id) values (?,?,?,default) Hibernate: insert into products (description,name,price,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) 2024-10-24T16:48:45.238Z INFO 25901 --- [p-nio-80-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-10-24T16:48:45.239Z INFO 25901 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-10-24T16:48:45.241Z INFO 25901 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms 2024-10-24T22:07:52.987Z INFO 25901 --- [p-nio-80-exec-9] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in the request target [/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello ]. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:482) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:263) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar!/:na] at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na] 2024-10-26T09:40:33.557Z INFO 25901 --- [p-nio-80-exec-7] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in the request target [/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello ]. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:482) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:263) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar!/:na] at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na] 2024-10-27T07:24:26.673Z WARN 25901 --- [p-nio-80-exec-6] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [media../.git/config]" 2024-10-27T07:24:26.674Z WARN 25901 --- [-nio-80-exec-10] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [docs../.git/config]" 2024-10-27T07:24:26.679Z WARN 25901 --- [p-nio-80-exec-3] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [static../.git/config]" 2024-10-27T11:41:00.134Z INFO 25901 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-10-27T11:41:00.140Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-10-27T11:41:00.167Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. (사진) (사진) (사진) (사진)
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 원인은 여러가지일 수 있는데, 헬스체크 기간동안 애플리케이션이 제대로 동작되지 않아 alb에서 비정상 상태로 파악하고 인스턴스를 종료시켰을 수 있습니다. 좀 더 정확한 원인 파악을 위해 target group에서 Unhealthy 가 된 원인을 확인할 수 있습니다. 해당 원인을 확인해서 알려주시며 도움드리기 수월할것같습니다!답변해주신 내용을 확인해봤는데, goopang-target-group 에서는 Health checks failed 로만 나오는데, 혹시 target-group 에서 error log 나 다른 내용을 확인하는 다른 방법이 있을까요? private-ec2-instnace 의 EC2 Instance Connect Endpoint 를 통한 cloud-init-output.log 로는 아래 내용 말고는 에러의 원인으로 보일만한 것이 없는 것 같아서요.2024-10-27T07:24:26.673Z WARN 25901 --- [p-nio-80-exec-6] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [media../.git/config]" 2024-10-27T07:24:26.674Z WARN 25901 --- [-nio-80-exec-10] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [docs../.git/config]" 2024-10-27T07:24:26.679Z WARN 25901 --- [p-nio-80-exec-3] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [static../.git/config]" 2024-10-27T11:41:00.134Z INFO 25901 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-10-27T11:41:00.140Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-10-27T11:41:00.167Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.(사진)
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님.private-ec2-instance 를 며칠동안 stop 시켜놨다가 다시 run 시켰더니 또 target-group 에서 Unhealthy 라고 떴습니다.그래서 이번엔 privaet-ec2-instance 에 EC2 Instance Connect Endpoint 를 이용하여 직접 접속한 후, cat /var/log/cloud-init-output.log 명령어를 통해 log 를 확인하였더니,서버가 잘 떴다가 아래의 에러가 발생합니다. (request target 에 문제가 있는 것 같습니다.)어느 부분에 문제가 있는건지 잘 모르겠어서 다시 한번 질문드립니다.. bernate: select count(*) from users u1_0 Hibernate: select count(*) from products p1_0 Hibernate: select count(*) from cart_items c1_0 Hibernate: insert into users (email,name,password,id) values (?,?,?,default) Hibernate: insert into users (email,name,password,id) values (?,?,?,default) Hibernate: insert into products (description,name,price,id) values (?,?,?,default) Hibernate: insert into products (description,name,price,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) Hibernate: insert into cart_items (product_id,quantity,user_id,id) values (?,?,?,default) 2024-10-24T16:48:45.238Z INFO 25901 --- [p-nio-80-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-10-24T16:48:45.239Z INFO 25901 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-10-24T16:48:45.241Z INFO 25901 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms 2024-10-24T22:07:52.987Z INFO 25901 --- [p-nio-80-exec-9] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in the request target [/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello ]. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:482) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:263) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar!/:na] at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na] 2024-10-26T09:40:33.557Z INFO 25901 --- [p-nio-80-exec-7] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in the request target [/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello ]. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:482) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:263) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar!/:na] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar!/:na] at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na] 2024-10-27T07:24:26.673Z WARN 25901 --- [p-nio-80-exec-6] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [media../.git/config]" 2024-10-27T07:24:26.674Z WARN 25901 --- [-nio-80-exec-10] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [docs../.git/config]" 2024-10-27T07:24:26.679Z WARN 25901 --- [p-nio-80-exec-3] o.s.w.s.r.ResourceHttpRequestHandler : "Path contains "../" after call to StringUtils#cleanPath: [static../.git/config]" 2024-10-27T11:41:00.134Z INFO 25901 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-10-27T11:41:00.140Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-10-27T11:41:00.167Z INFO 25901 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. Cloud-init v. 22.2.2 running 'init' at Wed, 30 Oct 2024 16:00:46 +0000. Up 4.53 seconds. ci-info: ++++++++++++++++++++++++++++++++++++++Net device info++++++++++++++++++++++++++++++++++++++
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 말씀해주신 2가지 방법 다 적용해보았습니다.1번. Private ec2 에 접속해서 스크립트 명령어를 직접 입력 -> EC2 Instance Connect Endpoint 를 이용해서 인스턴스에 접속했고, 명령어가 모두 정상적으로 실행되었습니다. 심지어 sudo java -jar build/libs/aws-msa-monolithic-prac-0.1.jar private ec2 에 접속한 상태에서 해당 명령어로 서버를 실행했을 때 alb 의 target group 이 healthy 상태로 표시됩니다.2번. Nat instance 대신 Nat gateway를 사용해보기 -> 이건 여전히 문제가 발생했었습니다.이 2가지 결과를 보았을 때, Private ec2 instance 를 run 할 때, User Data 의 스크립트에 문제가 있는 것으로 보고 혹시나 하는 마음에 모든 스크립트 내용을 개행 시켜줬습니다. 그렇게 해서 문제 해결했습니다!!백엔드 개발자지만 직접 이렇게 만져본 경험은 많지 않아서 많이 헤맸는데 강사님의 빠른 답변으로 속 시원하게 문제 해결했습니다. 감사합니다!
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 매번 빠른 답변 정말 감사합니다.넵 그래서 확인해봤는데, goopang-nat-instance 가 goopang-private-routing-table 에 route 로 설정이 제대로 되어있고, ping-test-instance 로 외부로 트래픽이 잘 나가는지 테스트도 해보았는데, ping 테스트도 정상적으로 작동합니다..그래도 여전히 문제가 발생하네요..(참고로 route table 의 Target 에 있는 eni-xxxx 는 Instance 설정 후 goopang-nat-instance 로 선택 후 저장하니까 네트워크 인터페이스로 자동 설정되었습니다.)(사진) (사진) 혹시나 vpc, subnet, route tables 설정 때 부터 잘못되었나 싶어서 앞의 강의들도 재시청했는데, 설정은 강사님이 하신데로 제대로 되어있었습니다..그래도 혹시 추가로 체크해야할 부분이 있을까요?
- 1
- 11
- 467
질문&답변
goopang-target-group 에서 unhealthy
안녕하세요 강사님. 늦은 밤에 빠른 답변 정말 감사합니다.그런데 private-ec2-instance, NAT-Instance 를 둘 다 t4g.small 에서 t4g.medium 으로 instance type 을 변경했는데도 여전히 unhealthy 로 확인됩니다..현재 bastion-host-instance 에 접속해서 private-ec2-instance 로 pem키를 이용한 ssh 접속은 정상적으로 진행되나 섹션 5 Private EC2 Instance 접속(실습) 에서와 같이 curl -XGET 으로 health_check 요청은curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server 와 같이 실패합니다.cat /var/log/cloud-init-output.log 명령어로 log 를 확인해봤을 때 아래와 같은 에러가 발생합니다.git, JDK, git clone 이 모두 실패하는 것으로 보이는데 원인을 잘 모르겠습니다.. User Data 에 스크립트는 정확하게 들어있는 것 확인했습니다.Cloud-init v. 22.2.2 running 'modules:config' at Tue, 22 Oct 2024 14:53:05 +0000. Up 7.18 seconds. Cloud-init v. 22.2.2 running 'modules:final' at Tue, 22 Oct 2024 14:53:06 +0000. Up 7.95 seconds. Amazon Linux 2023 repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'amazonlinux': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30001 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30002 ms] Error: Failed to download metadata for repo 'amazonlinux': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30001 ms] Amazon Linux 2023 Kernel Livepatch repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'kernel-livepatch': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Failed to connect to al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com port 443 after 30001 ms: Timeout was reached] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Failed to connect to al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com port 443 after 30000 ms: Timeout was reached] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Failed to connect to al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com port 443 after 30002 ms: Timeout was reached] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30002 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30000 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30001 ms] Error: Failed to download metadata for repo 'kernel-livepatch': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30000 ms] Ignoring repositories: amazonlinux, kernel-livepatch Error encountered while trying to retrieve release update information: Unable to retrieve release info data. Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/releasemd.xml [Connection timeout after 30001 ms] Dependencies resolved. Nothing to do. Complete! Amazon Linux 2023 repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'amazonlinux': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30000 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30001 ms] Error: Failed to download metadata for repo 'amazonlinux': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30000 ms] Amazon Linux 2023 Kernel Livepatch repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'kernel-livepatch': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30000 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30001 ms] Error: Failed to download metadata for repo 'kernel-livepatch': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30001 ms] Ignoring repositories: amazonlinux, kernel-livepatch No match for argument: java-17-amazon-corretto-devel Error: Unable to find a match: java-17-amazon-corretto-devel Amazon Linux 2023 repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'amazonlinux': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30002 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30001 ms] Error: Failed to download metadata for repo 'amazonlinux': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/core/mirrors/2023.6.20241010/aarch64/mirror.list [Connection timeout after 30001 ms] Amazon Linux 2023 Kernel Livepatch repository 0.0 B/s | 0 B 06:00 Errors during downloading metadata for repository 'kernel-livepatch': - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30001 ms] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Failed to connect to al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com port 443 after 30001 ms: Timeout was reached] - Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30002 ms] Error: Failed to download metadata for repo 'kernel-livepatch': Cannot prepare internal mirrorlist: Curl error (28): Timeout was reached for https://al2023-repos-ap-northeast-2-de612dc2.s3.dualstack.ap-northeast-2.amazonaws.com/kernel-livepatch/mirrors/al2023/aarch64/mirror.list [Connection timeout after 30001 ms] Ignoring repositories: amazonlinux, kernel-livepatch No match for argument: git Error: Unable to find a match: git /var/lib/cloud/instance/scripts/part-001: line 11: git: command not found /var/lib/cloud/instance/scripts/part-001: line 14: cd: aws-operation-prac: No such file or directory /var/lib/cloud/instance/scripts/part-001: line 17: ./gradlew: No such file or directory sudo: java: command not found 2024-10-22 15:31:09,338 - cc_scripts_user.py[WARNING]: Failed to run module scripts-user (scripts in /var/lib/cloud/instance/scripts) 2024-10-22 15:31:09,340 - util.py[WARNING]: Running module scripts-user () failed Cloud-init v. 22.2.2 finished at Tue, 22 Oct 2024 15:31:09 +0000. Datasource DataSourceEc2. Up 2290.97 seconds Cloud-init v. 22.2.2 running 'init' at Tue, 22 Oct 2024 16:16:20 +0000. Up 5.11 seconds. 혹시 몰라서 User Data 내용도 함께 올리겠습니다. #!/bin/bash #패키지 업데이트 sudo yum update -y #Java, Git 설치 sudo yum install -y java-17-amazon-corretto-headless sudo yum install -y git #Git 레포지토리 클론 및 브랜치로 이동 git clone -b 2_monolithic_cloud https://github.com/burger-2023/aws-operation-prac.git #폴더 이동 cd aws-operation-prac #Gradle을 이용한 Spring Boot 프로젝트 빌드 후 빌드된 Spring Boot 애플리케이션 실행 ./gradlew build sudo java -jar build/libs/aws-msa-monolithic-prac-0.1.jar
- 1
- 11
- 467
블로그
전체 42025. 04. 01.
0
[인프런 워밍업 클럽 3기] BE 클린코드&테스트 - 4주차 발자국
💡 강의 핵심 내용 정리💻Practical Testing🔹 1. 왜 Mocking이 필요한가?외부 시스템(메일 전송, 결제, 알림 등)을 호출하는 코드는 실제 실행하면 부작용이 발생할 수 있음.테스트 시 외부 의존성을 제거하고 예측 가능한 결과를 주기 위해 Mock 객체를 사용함.이 과정을 Stubbing이라 하며, 원하는 동작을 가짜 객체에게 명시함.Mockito.when(mailSendClient.sendEmail(...)).thenReturn(true); 🔹 2. Mail 전송은 @Transactional을 붙이면 안 되는 이유메일 전송은 외부 네트워크 요청이며, 트랜잭션 범위 안에서 실행되면 DB Connection을 오래 점유하게 됨.이런 긴 작업은 트랜잭션 바깥에서 실행되어야 함.🔹 3. Test Double의 종류유형 설명 Dummy 사용되지 않는, 껍데기 객체 Fake 간단한 구현을 가진 실제 객체 (Map 기반 Repository 등) Stub 미리 정의된 응답을 제공하는 객체 (상태 검증용) Spy 일부는 실제처럼, 일부는 Stub. 호출 기록 추적 가능 Mock 행위 기반 검증용 객체 (몇 번 호출되었는지 등 검증)💡 Stub은 상태 검증, Mock은 행위 검증에 사용됨.🔹 4. 순수 Mockito 사용법@Mock, @InjectMocks, @ExtendWith(MockitoExtension.class) 조합으로 Spring Context 없이도 테스트 가능@Spy: 실제 객체 기반으로 필요한 부분만 StubbingdoReturn(true).when(mailSendClient).sendEmail(...); 🔹 5. BDDMockitowhen(...).thenReturn(...) 대신 Given-When-Then 스타일을 지향BDDMockito.given(mailSendClient.sendEmail(...)).willReturn(true); 테스트의 목적과 구조가 명확하고 선언적으로 표현됨.🔹 6. Classicist vs Mockist구분 Classicist Mockist 테스트 단위 실제 객체로 통합 테스트 협력 객체는 모두 Mock 강조점 시스템 동작 검증 객체 간 상호작용 검증 사용 시점 DB, HTTP 연동 등 진짜 동작 필요할 때 외부 의존이 많거나, 로직 복잡도 높은 객체✅ 일반적으로는 Classicist 접근을 사용하고, 외부 시스템 등 불가피한 경우에만 Mocking을 하자!🔹 7. 테스트 코드 개선 전략 요약📌 한 문단에는 한 주제테스트는 명확하게 하나의 동작만 검증해야 함.if, for 등의 분기/반복문이 테스트 코드에 들어가면 테스트 목적이 흐려짐.📌 제어 가능한 값만 사용하라현재 시간, UUID, 랜덤, 환경 변수 등은 직접 주입하거나 인터페이스로 추출하여 테스트 가능하게 설계.📌 테스트 간 독립성 보장static 공유 객체 금지 (@BeforeAll/@BeforeEach도 주의)fixture는 되도록 각 테스트 내에서 명시적으로 구성📌 Test Fixture는 생성자 기반 / Builder 활용팩토리 메서드 대신 Builder 패턴 선호data.sql로 데이터 주입 지양 → 테스트 목적 파악 어려움📌@ParameterizedTest, @DynamicTest 적극 활용경계값, 다양한 조건 검증이 필요한 경우 유용@DynamicTest는 상태 변화 시나리오 테스트에 특히 적합📌 공통 테스트 환경 통합@SpringBootTest를 반복 호출하지 않도록 상위 추상 클래스로 환경을 공통화@SpringBootTest @ActiveProfiles("test") public abstract class IntegrationTestSupport {} 📌 private 메서드는 테스트하지 말자테스트가 필요할 정도로 복잡해졌다면, 새로운 객체로 추출해서 테스트 가능하도록 리팩토링.
2025. 03. 23.
0
[인프런 워밍업 클럽 3기] BE 클린코드&테스트 - 3주차 발자국
💡 강의 핵심 내용 정리💻Practical Testing✅ 레이어드 아키텍처 (Layered Architecture)목적: 관심사 분리 → 책임을 나누고 유지보수 용이하게4계층 구성: Presentation → Business → Persistence → Infrastructure✅ Spring & JPA 핵심 개념Library vs FrameworkLibrary: 내가 주도Framework: 프레임이 주도 (내 코드는 수동적으로 참여)Spring 핵심 개념IoC: 객체 생명주기 관리DI: 외부에서 의존성 주입AOP: 핵심 로직과 공통 관심사 분리 (Spring은 프록시 기반)JPA / ORMORM: 객체 관계형 DB 간 불일치 해결JPA: Java 진영 ORM 인터페이스 (Hibernate 사용 많음)✅ Persistence Layer 테스트역할: 순수 Data 접근 (비즈니스 로직 X)목적:쿼리가 의도대로 작동하는지 검증향후 변경 가능성 대비어노테이션@DataJpaTest: JPA 관련 Bean만 등록 (가볍고 빠름)@ActiveProfiles("test"): 테스트용 프로파일 적용✅ Business Layer 테스트 (Service)역할: 비즈니스 로직 구현, Persistence Layer와 상호작용테스트 방식: 통합 테스트 (@SpringBootTest)주의: @Transactional 남용 주의 → 서비스에 선언된 트랜잭션 유무를 놓칠 수 있음JPA의 변경감지save, delete는 내부적으로 @Transactional 적용되어 변경 감지 작동✅ Presentation Layer 테스트 (Controller)역할: 외부 요청의 진입 지점 / 파라미터 검증테스트 방식@WebMvcTest: 컨트롤러 중심 테스트 (다른 레이어는 @MockBean 사용)MockMvc: HTTP 요청/응답 흐름을 시뮬레이션✅ @Transactional(readOnly = true) 활용용도: 읽기 전용 트랜잭션 → 변경감지 비활성화 → 성능 향상CQRS 설계 패턴명령(Command) / 조회(Query) 분리서비스 분리, API URL 분리, DB Master-Slave 분리 등으로 확장 가능
2025. 03. 16.
0
[인프런 워밍업 클럽 3기] BE 클린코드&테스트 - 2주차 발자국
💡 강의 핵심 내용 정리📝Readable Code1. 주석의 역할과 사용 원칙🚫 주석을 최소화해야 하는 이유주석이 많다는 것은 비즈니스 요구사항을 코드로 잘 표현하지 못한 것을 의미한다.주석에 의존하면 적절한 추상화 수준을 유지하기 어려워 코드 품질이 낮아진다.✅ 언제 주석을 사용해야 할까?코드로 표현할 수 없는 의사 결정의 히스토리를 남길 때.자주 변경되지 않는 정보만 포함하고, 지속적으로 관리해야 한다.부정확한 주석은 차라리 없는 것보다 나쁘다.2. 변수와 메서드 정렬 순서🔍 변수 정렬 원칙변수는 사용하는 순서대로 배치하여 가독성을 높인다.📌 메서드 정렬 원칙공개 메서드 (public) 먼저 배치상태 변경 > 판별 > 조회 순으로 배치.같은 기능을 하는 메서드는 그룹화하여 정렬.**비공개 메서드 (private)**는 공개 메서드에서 호출하는 순서대로 정렬.3. 코드 리팩토링 & 가독성 향상🏗 주석 없이 코드로 의미 전달하기 (Enum 활용)java복사편집// ❌ 기존 코드 private int gameStatus = 0; // 0: 게임 중, 1: 승리, -1: 패배 // ✅ 개선 코드 public enum GameStatus { IN_PROGRESS("진행 중"), WIN("승리"), LOSE("패배"); }🎯 객체 역할 분리GameBoard → 게임 진행 및 로직 담당 (비즈니스 로직)MineSweeper → 사용자 입력/출력 제어 (컨트롤러 역할)4. 패키지 구조 개선📌 패키지는 "문맥"을 제공한다유지보수를 고려하여 적절한 수준으로 패키지를 나눈다.패키지 변경은 팀원과 협의 후 진행하는 것이 좋다.5. IDE 도구 활용⚙ 코드 자동 정렬Option + Command + L (Mac, IntelliJ)🔍 코드 품질 유지 도구SonarCube, lint (ESLint, ktlint).editorconfig를 활용해 일관된 코드 스타일 유지6. 오버 엔지니어링 방지🚫 불필요한 추상화 & 인터페이스 남발구현체가 하나뿐인 인터페이스는 불필요한 경우가 많다.추상화를 도입하면 정보가 숨겨지므로 복잡성이 증가.필요 이상으로 정보를 숨기면 코드 유지보수가 어려워진다.7. 실무에서 코드 품질 vs 빠른 개발⚖ 균형 잡힌 선택이 필요하다완벽한 코드보다 실용적인 코드가 중요하다.클린 코드를 목표로 하되, 현실적인 개발 속도도 고려해야 한다.TODO 주석 등을 활용하여 미래 리팩토링을 위한 가이드 남기기.8. 결론: "완벽한 코드"는 없다클린 코드도 은탄환이 아니다. 적절한 수준에서 적용하는 것이 중요하다.테스트 코드도 잘 관리하지 않으면 오히려 유지보수 부담이 될 수 있다.가장 중요한 것은 도메인 지식을 늘리고, 코드가 어떤 비즈니스 요구사항을 해결하는지 파악하는 것 💻Practical Testing1. 테스트 코드의 필요성🤔 왜 테스트 코드가 중요한가?자동화된 테스트를 통해 빠르고 정확한 피드백 제공.사람이 직접 테스트하면 비효율적이고 실수가 발생할 가능성이 높음.🚫 수동 테스트의 문제점java복사편집@Test void add_manual_test() { CafeKiosk cafeKiosk = new CafeKiosk(); cafeKiosk.add(new Americano()); System.out.println(">>> 담긴 음료 수 : " + cafeKiosk.getBeverages().size()); } ❌ 사람이 직접 확인해야 하므로 자동화되지 않고 오류가 발생할 가능성이 높음.✅ JUnit5 & AssertJ 활용한 자동화 테스트java복사편집@Test void add() { CafeKiosk cafeKiosk = new CafeKiosk(); cafeKiosk.add(new Americano()); assertThat(cafeKiosk.getBeverages()).hasSize(1); assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노"); } ✔ 자동화된 검증을 통해 일관성 유지✔ 테스트 실패 시 바로 원인 파악 가능2. 테스트 케이스 설계🔍 경계값 테스트범위 (이상, 이하, 초과, 미만), 구간, 날짜 등 고려하여 테스트 설계. 💡 미션 Day 7이번주 미션은 StudyCafe 라는 프로젝트(스터디 카페 이용권 구매 서비스)를 리팩토링하는 미션이었다.하지만, 시간이 부족하여 내 자신이 만족할 만한 코드를 작성하지 못하였고, 계속 수정하다가 결국 마감 시간 보다도 늦게 제출하고 말았다...이후에, 중간점검 날 다른 분들의 피드백도 보고 깨달은 바가 많았다. 꼭 다시 새로운 마음으로 리팩토링 하여 마감 기한에 상관 없이 다시 제출해보고, 지금 작성 중인 2주차 발자국에도 내가 어떤 과정 및 생각으로 미션을 수행했는지 업데이트 할 것이다.💡 한주 회고이번 한주도 엄청 바빴다. 읽기 좋은 코드 강의를 마무리하고 새로 테스트 코드 강의로 들어섰다. 이전 회사에서 테스트 코드를 제대로 짜본 적이 없고 또 그럴 환경이 뒷바쳐 주지 못해서 작성하지 못하였다. 그래서, 테스트 코드에 대한 갈증이 더욱 컸다. 이번 기회에 제대로 배우고 시야를 넓혀가야겠다. Day 11 미션에는 꼭 강사님께 코드 리뷰 제출까지 해봐야겠다. 화이팅!!
2025. 03. 09.
0
[인프런 워밍업 클럽 3기] BE 클린코드&테스트 - 1주차 발자국
강의 수강 내용 핵심 정리1. 클린 코드와 추상화클린 코드의 핵심은 가독성 → 코드가 잘 읽히면 이해하기 쉽고 유지보수가 용이함.추상화(Abstract): 구체적인 정보에서 핵심 개념만 뽑아 단순화하는 과정.적절한 추상화는 복잡한 로직과 데이터를 단순화하여 읽기 좋은 코드를 만든다.잘못된 추상화는 문맥이 맞지 않는 용어 사용이나 과도한 단순화로 혼란을 유발할 수 있음.추상화 레벨을 맞추는 것이 중요 → 동일한 코드 블록 내에서는 비슷한 추상화 수준 유지.2. 이름 짓기(Naming)의도를 명확히 전달하는 이름을 사용 (e.g. input, input2 → cellInput, userActionInput).줄임말은 가독성을 해칠 수 있음 → 되도록 풀네임 사용 (e.g. cnt 대신 count).일관된 도메인 용어 사용 → 내부 팀에서 정한 용어를 지키기.단수/복수 명확히 구분 (e.g. row vs rows).getter/setter 사용 최소화 → 객체에 메시지를 보내도록 유도 (isAgeGreaterThan(19)).3. 메서드 설계와 선언부메서드는 하나의 책임(기능)만 가지도록 설계 → 한 가지 이상의 일을 하면 분리.메서드명은 추상적 의미를 담아야 함 (예: processPayment 대신 deductBalance).파라미터와 반환 타입을 신중하게 결정 → void 대신 반환값을 활용하여 테스트 가능하도록 함.메서드의 Depth(중첩 수준) 최소화 → 지나치게 깊은 if문, for문은 리팩토링 필요.4. 가독성을 위한 코드 스타일Early Return 사용 → 불필요한 else 제거로 가독성 향상.공백 라인 활용 → 의미 있는 단위로 코드 분리.부정문 지양 → !isLeft() 대신 isRight() 또는 isNotLeft().매직 넘버 & 매직 스트링 제거 → 10 대신 BOARD_SIZE 사용.변수는 사용되는 곳 가까이 선언 → 코드 이해도를 높이고 유지보수 용이.5. 객체지향 설계 원칙 (SOLID)(1) 단일 책임 원칙 (SRP)한 클래스는 하나의 책임(변경 이유)만 가져야 함.예) 게임 로직과 사용자 입력 처리를 분리 (e.g. GameLogic, UserInputHandler).(2) 개방-폐쇄 원칙 (OCP)기존 코드 수정 없이 기능 확장이 가능해야 함.추상화(인터페이스) 사용으로 변경 사항이 최소화되도록 설계.(3) 리스코프 치환 원칙 (LSP)부모 클래스를 자식 클래스로 대체해도 문제가 없어야 함.자식 클래스에서 부모 클래스의 기능을 임의로 변경하거나 지원하지 않는 기능을 추가하면 LSP 위반.(4) 인터페이스 분리 원칙 (ISP)하나의 인터페이스가 너무 많은 기능을 포함하면 안 됨 → 기능 단위로 분리.예) GameInitializable, GameRunnable로 인터페이스 분리.(5) 의존성 역전 원칙 (DIP)상위 모듈(비즈니스 로직)은 하위 모듈(구현)과 직접적으로 의존하지 않아야 함.인터페이스를 통해 의존성을 주입(DI) → 런타임에 유연한 변경 가능.스프링의 IoC 컨테이너를 활용하면 DIP 자동 적용 가능.6. 예외 처리와 NULL 다루기예외를 의도적으로 구분 → 사용자 예외(AppException) vs 시스템 예외.NULL 사용 최소화 → Optional 활용, orElseGet()으로 성능 최적화.예외 발생 가능성이 높은 구간은 외부 세계와의 접점 (e.g. 입력값, API 응답).7. 코드 리팩토링 접근법처음부터 SOLID 원칙을 완벽하게 적용하려 하지 말고, 도메인 이해를 우선.코드를 작성하면서 점진적으로 리팩토링 → 적절한 추상화와 역할 분리를 고민하며 개선.성능, 유지보수성을 고려하여 때로는 원칙을 트레이드오프할 수도 있음.8. 상속보다는 조합을 사용하자상속은 부모-자식 간 결합도가 높아 수정이 어렵고 유연성이 떨어짐.부모 클래스 변경 시 모든 자식 클래스에 영향을 미침.조합(Composition)과 인터페이스를 활용하면 유연한 구조를 만들 수 있음.코드 중복 제거보다 유연한 설계가 더 중요하다.9. Value Object(VO)와 EntityVO(Value Object): 도메인의 개념을 표현하는 값 객체로, 불변성을 가지며 식별자가 없음.불변성: final 필드 사용, setter 금지.동등성: 값이 같으면 동일한 객체로 취급 → equals() & hashCode() 재정의 필요.유효성 검증: 객체 생성 시점에서 검증 수행.Entity: 식별자가 존재하며, 같은 ID를 가지면 같은 객체로 취급.VO와 Entity의 차이:Entity: 시간이 지나면서 값이 변할 수 있음.VO: 생성된 이후 값이 변하지 않으며, 모든 값이 같아야 같은 객체로 취급됨.→ equals() & hashCode()를 재정의해야 하는 이유와, hash 자료형을 구현하는 방법을 학습할 필요가 있음.10. 일급 컬렉션(First-Class Collection)컬렉션을 단순히 사용하지 않고 객체로 포장하여 의미를 부여함.컬렉션을 감싸면서 로직을 함께 관리 → 가공 로직을 포함하여 유지보수성을 높임.새로운 컬렉션을 반환해야 하는 경우기존 컬렉션을 변경할 여지를 없애기 위해 새로운 리스트(ArrayList 등)를 반환해야 할 때가 있음.예: new ArrayList(originalList)는 리스트 객체를 새로 만들지만 내부 요소는 기존 객체를 참조하므로, 원본 데이터를 안전하게 유지하려면 내부 객체도 깊은 복사(Deep Copy)해야 함.11. Enum의 특성과 활용Enum은 단순한 상수가 아닌, 상태와 관련된 로직을 포함할 수 있는 객체.특정 도메인의 개념을 명확하게 표현할 수 있음.변경이 잦은 개념이라면 Enum 대신 DB에서 관리하는 것이 유리함.12. 다형성(Polymorphism) 활용하기반복적인 if-else 문을 줄이기 위해 다형성을 적극 활용.변하는 것은 조건과 행위 → enum + 인터페이스 조합으로 해결 가능.Enum value별로 인터페이스를 구현할 수 있음.한 번 배운 개념도 반복해서 복습하는 것이 중요하다.13. 숨겨진 도메인 개념 도출하기객체 지향은 현실을 100% 반영하는 것이 아니라, 현실을 흉내 내는 것.완벽한 설계는 불가능하며, 그 당시의 최선의 선택이 중요함.시간이 지나면서 틀렸음을 인지할 수도 있기 때문에, 미래 변경 가능성을 고려한 코드 작성이 필요함.미션 해결 과정 정리Day 2 미션우리는 매일 숨을 쉬고 음식을 소화하지만, 이를 매번 세밀하게 설명한다면 너무 복잡하고 불편할 것입니다.이 미션을 수행하면서, 최근 읽고 있는 《데이터 중심 애플리케이션 설계》(a.k.a DDIA)에서 본 문장이 떠올랐습니다."우발적 복잡도(accidental complexity)를 제거하기 위한 최상의 도구는 추상화다."여기서도 추상화라는 개념이 강조됩니다. 또한, 책의 예시에서도 “기계어, CPU 레지스터, 시스템 호출을 추상화한 것이 고수준 프로그래밍 언어이다”라는 설명이 나옵니다. 이는 강의에서 들었던 내용과도 동일합니다.컴퓨터가 실제로 실행하는 것은 어셈블리어와 같은 저수준 코드이지만, 개발자인 우리가 이를 직접 다루는 것은 어렵고 비효율적입니다. 따라서, 우리는 Java 같은 고수준 프로그래밍 언어를 사용하며 자연스럽게 추상화의 이점을 누리고 있는 것입니다.또 다른 추상화의 예시로 떠오른 것은 Spring에서 Database(ex: MySQL)를 사용하기 위한 ORM인 JPA, Spring Data JPA입니다.우리는 데이터베이스를 사용하기 위해 보통 다음과 같은 작업을 수행해야 합니다.Connection Pool에서 Connection을 가져옴데이터베이스와 연결을 맺음SQL 쿼리를 작성하고 실행결과를 받아서 처리이 과정은 데이터베이스를 사용할 때마다 반복되며, 이는 우발적 복잡도를 증가시킵니다. 이를 해결하기 위해 JPA와 Spring Data JPA 같은 ORM이 등장했습니다.즉, 선배 개발자들이 데이터베이스 작업의 복잡성을 추상화하여 보다 간결하고 일관된 방식으로 데이터를 다룰 수 있도록 한 것입니다.또한, Java의 interface 역시 추상화의 대표적인 사례라고 생각합니다.interface는 구현 세부 사항을 숨기고, 다양한 구현체를 유연하게 사용할 수 있도록 하며, 다형성을 활용하여 코드의 재사용성과 유지보수성을 높이는 역할을 합니다.이렇듯, 이번 미션에서는 다시 한번 올바른 추상화의 중요성과 그것이 Readable Code 를 만드는데 얼마나 큰 요소를 차지하는지 잘 알 수 있었습니다.애플리케이션 설계, 코드 구현 등 여러 곳에서 추상이라는 개념이 얼마나 중요하게 여기는지 다시 한번 알 수 있었고, 적재적소에 잘 써먹어야겠다는 생각을 많이 했습니다.Day 4 미션AS-ISpublic boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; } Readable 하지 못한 점 파악if-else 가 중첩되어 있다. → 사고의 depth 를 줄이자!early return 을 하자!부정 연산자(!)의 가독성이 떨어진다.getter 의 연속 사용 → 메서드화 하자!TO-BE@Service @RequiredArgsConstructor public class OrderService { ... public boolean validateOrder(Order order) { if (order.hasNoItems()) { throw new OrderException("주문 항목이 없습니다."); } if (order.hasNotCustomerInfo()) { throw new OrderException("사용자 정보가 없습니다."); } if (order.isInValidTotalPrice())) { throw new OrderException("올바르지 않은 총 가격입니다."); } return true; } } @Entity @Table(name = "orders") @Where(clause = "deleted_at IS NULL") @SQLDelete(sql = "UPDATE orders SET deleted_at = NOW() WHERE id = ?") public class Order { ... private boolean hasNoItems() { return this.items.isEmpty(); } private boolean hasNotCustomerInfo() { return this.customInfo.isEmpty(); } private boolean isTotalPriceLessThanZero() { return this.items.stream().mapToInt(Item::getPrice).sum() public class OrderException extends RuntimeException { public OrderException(String message) { super(message); } } if-else 중첩문을 if 문 3개로 변환했다.order.getItems().size() == 0 getter 의 연속적 사용을 메서드화하여 Order 객체 안으로 숨겨서 캡슐화했다.!order.hasCustomerInfo() 과 같은 부정 연산자를 없애고, hasNotCustomerInfo 처럼 부정어도 메서드화 하여 가독성을 높였다.추가적으로 if 문 조건에 들어가는 메서드들을 Order class 안으로 옮기고,단순히 log 처리만 하는 것을 넘어서 RuntimeException 인 OrderException 으로 예외처리를 하였다.SOLID에 대하여 자기만의 언어로 정리해 봅시다.SRP : 단일 책임 원칙(single responsibility principle)OCP : 개방-폐쇄 원칙 (Open/closed principle)LSP : 리스코프 치환 원칙 (Liskov substitution principle)ISP : 인터페이스 분리 원칙 (Interface segregation principle)DIP : 의존관계 역전 원칙 (Dependency inversion principle)SRP(단일 책임 원칙)한 클래스는 하나의 책임만 가져야 한다.중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것OCP(개방-폐쇄 원칙)소프트웨어 요소는 확장에 열려 있으나 변경에는 닫혀 있어야 한다.다형성을 활용해보자인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현하면 코드에 변경이 없다.LSP(리스코프 치환 원칙)프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것ISP(인터페이스 분리 원칙)특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다덩어리가 크면 그걸 다 구현하기가 힘들다. 덩어리가 작으면 작은 기능만 구현하면 되니까 훨씬 쉬워진다.인터페이스가 명확해지고, 대체 가능성이 높아진다.DIP(의존관계 역전 원칙)프로그래머는 추상화(역할)에 의존해야지, 구체화(구현)에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다.쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻앞에서 이야기한 역할(Role)에 의존하게 해야 한다는 것과 같다.객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하게 되면 변경이 아주 어려워진다.개인적으로 Spring Bible 로 생각하는 토비의 스프링, 김영한님의 Spring 강의를 듣고 채득한 내용을 바탕으로 SOLID 개념을 정리해봤습니다.일주일 회고다른 워밍업 클럽과는 달리 한 달 안에 2개의 강의, 그것도 강의당 대략 13시간 정도의 강의를 수강해야 해서 강의를 빨리 들어야 하는 압박감이 들어서 개인적으로는 쉽지 않았던 것 같습니다.그래도, 진도에 늦지 않게 강의를 수강한 것 같아서 무척 뿌듯하네요.그리고, 이번주에는 미션이 2개 있었는데 해당 미션을 수행하면서 강사님이 중요하게 생각하는 건 무엇인지, 강의를 들을 때 어떤 걸 중점적으로 들어야 하는지에 대한 일종의 가이드라인을 주시는 것 같아서 강의를 수강하기에 좀 더 수월 했던 것 같습니다.현재, 2년 2개월을 회사 생활을 마치고, 이직 준비를 한지 벌써 1년이 지나면서 바쁘게 취직 준비를 하고 있지만 그럼에도 박우빈님의 강의를 들으면서 클린코드/테스트코드를 공부하기 정말 잘했다는 소위 말하는 돈값을 한다는 강의를 느꼈습니다.남은 3주도 열심히 하여 완강은 물론 우수러너도 되어보도록 하겠습니다.