728x90
반응형
728x90
반응형
728x90
반응형

문제 발생일 : 2022-06-10 13시 경

 

결론 부터 말하자면 ubuntu update시 문제로 보여진다.

 

나의 경우 정확히는 host 우분투에서 발생한 것은 아니다.

docker 컨테이너로 ubuntu를 실행 중 필요해서 서버를 리부팅하고 다시 컨테이너를 실행하려고 했더니 에러가 나는 상황.

한번 이미지를 만들어 놓으면 별다른 업데이트를 하지 않기 때문에 내가 업데이트를 진행한 케이스는 아니다.

내가 모르는 어떤 설정에 의해 컨테이너의 재시작 시 ubuntu가 자동으로 업데이트가 된 것으로 보인다.

(이미지에 update가 들어가면 재시작시 자동으로 update를 하나? ㅡㅡㅋ )

 

# 에러 메시지 =================================================================

Message from syslogd@server at Jun 10 13:32:35 ...
 kernel:[  129.502235] Internal error: Oops - BUG: 0 [#1] SMP

Message from syslogd@server at Jun 10 13:32:35 ...
 kernel:[  129.654261] Code: 91059283 52800020 1400016f 17ffffa0 (d4210000)
...

============================================================================

 

* 참고정보 1

 

서버포럼 - Oracle Cloud A1 서버 apt upgrade 실행 하지마세요 - 추가) 커널 업그레이드????

++ 추가 ++ 부트볼륨 백업을 적용 했다가 계속 이상해서 새로운 마음으로 다시 설치 중입니다. 아래 메시지가 뜨면서 커널 업그레이드 하네요. 기존 버전에서 매끄럽게 업그레이드가 안되는거 같

svrforum.com

 

* 참고정보 2 - 오라클 포럼 게시글 : https://community.oracle.com/customerconnect/discussion/634302/kernel-panic

포럼에 로그인을 안 하면 내용이 다 보이지 않는다.

요약하면 최근에 ubuntu를 업데이트 한 경우에 발생된 것으로 보여진다.

 

위 글들을 읽고 한참 후에야  '아~ docker의 ubuntu가 문제로 구나!' 라고 생각하고

다른 ubuntu 이미지를 받아 컨테이너로 실행해보니 다 동일한 에러가 나타나고 있다.

 

두개의 글 모두 최근 3일내이고, 구글 검색으로 확인되는 정보도 모두 3일내 정보다.

또한 docker hub의 ubuntu 최종 업데이트 일이 3일 전인 걸로 봐선.........

 

 

다행히도 당장 살려놔야 할 정도의 서버는 아닌지라 급하진 않은데...

이런 상태가 지속된다면 다른 OS로 갈아타는걸 생각해야 겠다.

 

 

220704 덧,

해결방법이 있다는 글이 있어 확인해 보려고 했으나 실수로 시도하지 않고 그냥 docker를 다시 살렸는데 잘 된다.

해결 방법이라는 글은 아래 글. (난 적용 안함.)

 

kernel:[ 15.642879] Code: 91059283 52800020 1400016f 17ffffa0 (d4210000) - Error when booting (Oracle cloud instance)

When I boot on my oracle cloud instance (running on the lastest ubuntu), since I installed the last update, my machine show me the error : Message from syslogd@ampere at Jun 9 18:45:43 ... kernel...

stackoverflow.com

그래서 내가 왜 해결된 건지, 뭔가 다른 패치가 된건지 잘 모르겠다.

그래도 혹시 몰라서 host는 업데이트를 안 하고 있다.

나의 경우 서슴없이 테스트 할 수 있었던 것도 어디까지나 docker 환경이기 때문이므로 아직은 쉽게 추천할 수 없다.

 

728x90
반응형
728x90
반응형

필요한 기능이 있어서 추가한 코드에서 갑작스런 에러가 났다.

원인은 간단하다. select 구문의 결과를 받을 resultType 또는 resultMap이 누락된거다.

 

1. SELECT COUNT(*) FROM MEMBER

WHERE MEM_TP = 'USER'

AND DEL_YN = 'N'

{executed in 8 msec}

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'packagename.core.schedule.mapper.ScheduleMapper.getMemberTypeCount'. It's likely that neither a Result Type nor a Result Map was specified.

at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)

at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)

at com.sun.proxy.$Proxy104.selectOne(Unknown Source)

at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)

at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)

at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:144)

at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)

at com.sun.proxy.$Proxy127.getMemberTypeCount(Unknown Source)

at packagename.core.schedule.service.ScheduleService.common(ScheduleService.java:966)

at packagename.core.schedule.service.ScheduleService.start(ScheduleService.java:793)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$5a06c0f7.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$370f28bf.startTistoryUrlCrawling()

at packagename.web.BaseController.getTest(BaseController.java:101)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Unknown Source)

Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'packagename.core.schedule.mapper.ScheduleMapper.getMemberTypeCount'. It's likely that neither a Result Type nor a Result Map was specified.

at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.validateResultMapsCount(DefaultResultSetHandler.java:289)

at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:191)

at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65)

at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)

at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)

at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)

at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)

at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)

at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108)

at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)

at com.sun.proxy.$Proxy193.query(Unknown Source)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)

... 71 more

 

 

 

 

언제나 느끼는 오늘의 교훈 ㅎ

수정하면 테스트 해보자. 에러 좀 자세히 봐라.

728x90
반응형
728x90
반응형

Integer인 각각의 A, B의 값이 같은지 비교하여, 다른 경우 C의 값을 2로 변경하는 코드가 있다.

그런데 A, B 모두 200 이었는데 C 값이 변경이 2로 변경된게 발견 되었다.

동일한 Integer이고, 둘다 200인데 왜 내 의도와 다른 결과가 나왔을까?

 

디버깅을 해봐도 분명 200으로 같은 값인데 뭐가 문제인가 싶어 검색을 해보니

두개의 Integer의 비교 시 == 또는 != 사용시 변수의 주소값을 비교하기 때문 이었다.

 

아래는 이를 보여주는 단순한 예시코드다

* 예시코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TestCode {
    
    public static void main(String[] args) {
        
        Integer integerA = 1;
        Integer integerB = 1;
        Integer integerC = new Integer(1);
        int intA = 1;
        int intB = 1;
        
        System.out.println("integerA : " + integerA);
        System.out.println("integerB : " + integerB);
        System.out.println("integerC : " + integerC);
        System.out.println("intA : " + intA);
        System.out.println("intB : " + intB);
        System.out.println("#################");
        System.out.println(integerA == 1);
        System.out.println(intA == 1);
        System.out.println(integerA == integerB);
        System.out.println(integerA == integerC);
        System.out.println(intA == intB);
        System.out.println(integerA == intA);
        System.out.println("#################");
        System.out.println(integerA.equals(1));
        System.out.println(integerA.equals(integerB));
        System.out.println(integerA.equals(integerC));
        System.out.println(integerA.equals(intA));
    }
}
cs

* 출력된 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
integerA : 1
integerB : 1
integerC : 1
intA : 1
intB : 1
#################
true
true
true
false
true
true
#################
true
true
true
true
cs

분명 같은 1을 갖고 있음에도 소스코드 20번 라인의 실행결과는 false로 나오고 있다.

그러므로 Integer 값에 대해 동일여부를 확인할때 == 또는 !=를 사용하고 있다면, 

내 의도와 다른 결과가 나올 수 있으므로 equals을 사용하자.

(더 곤란한건 exception도 없이 소스는 잘 돌아간다.)

 

 

추가로,

사실 이 이슈는 단순히 주소값 비교로 설명하면 안 된다.

Primitive Type, boxed Primitive Type, wrapper class 라는 개념에 관련된 내용이다.

이 내용을 설명하기에는 너무 길어지니 자세한 내용은 구글 검색을 추천!

728x90
반응형
728x90
반응형

시작은 단순하다.

실행중인 tomcat 서버의 로그는 계속해서 쌓여만 갔다.

특히 문제는 catalina.out 파일이 용량이 계속 늘어가고 있다는 것 이었다.

이렇게 방치하고 무한정 둔다면 언젠가 서버가 사망하게 되리라...

 

목표는 2가지다.

1. catalina.out 파일을 날짜별로 나눈다.

2. 일정기간 이상 오래된 파일은 삭제한다.

 

이걸 위해 검색해 본 결과 tomcat설치폴더/bin/catalina.sh 파일을 수정하라는 것 이었다.

근데 왠지 나는 실행하면 에러가 나니 검색된 내용을 반영할 수가 없었다.

또한 rotatelogs를 이용하라는데 나는 설치되어 있지 않다.

 

그래서 선택한 방법은 터미널 명령어와 cron이다.

 

1. 파일 관리를 위한 delete_log.sh 파일을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tomcat log folder location
TOMCAT_LOG=/tomcat/logs
 
# get today date
DATE="$(date '+%Y-%m-%d')"
 
# back up 'catalina.out' files by date
cp "$TOMCAT_LOG/catalina.out" "$TOMCAT_LOG/catalina.out.$DATE"
 
# catalina.out file reset
cat /dev/null > "$TOMCAT_LOG/catalina.out"
 
# delete file that is 30 days old
find "$TOMCAT_LOG" -mtime +30 -type f -exec rm -f {} \;
cs

내가 참고한 글에서는 5번 라인에 DATE='date +%Y_%m_%d' 라고 되어 있었다.

그러나 실행시 cp: target '+%Y-%m-%d' is not a directory 라는 에러가 났고, 위와 같이 수정 했더니 해결 되었다.

 

(1) 2번 라인은 당신이 tomcat을 설치한 위치로 변경해야 한다.

(2) 1~11번 라인까지는 catalina.out 파일을 오늘 날짜로 백업하고 기존 파일은 초기화 한다.

(3) 14번 라인은 30일 이상된 log 파일을 삭제한다

 

2. delete_log.sh 파일을 배치 시킨다.

이 파일을 원하는 위치에 배치한다. 예를들면 /home/ubuntu 폴더 아래에 둔다.

그리고 실행할 수 있도록 권한을 준다.

1
chmod 744 /home/ubuntu/delete_log.sh
cs

권한을 준 후에 'bash /home/ubuntu/delete_log.sh'를 실행하면

위에 작성한 delete_log.sh의 내용이 현재 시간 기준으로 진행되는 것을 확인 할 수 있다.

 

3. cron을 사용해서 delete_log.sh를 실행할 스케줄을 만들어 준다.

(1) 먼저 해야할 일은 cron을 설치하고 활성화 하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
# cron 설치
apt install -y cron
 
# cron 시작
service cron start
 
# cron systemctl 활성화
systemctl enable cron.service
 
# cron systemctl 등록 확인
systemctl list-unit-files | grep cron
cs

(만약 systemctl 명령어를 찾을 수 없다는 에러가 난다면 'apt install -y systemctl' 를 통해 설치)

 

(2) crontab 편집

1
crontab -e
cs

위 명령어를 입력해서 편집 화면으로 들어간다.

그리고 아래 내용을 가장 하단에 넣고 저장하여 종료한다.

1
2
3
#매일 23시 55분에 터미널 명령어 파일 실행
#분   시   일   월   주   명령어
55    23   *    *    *    /home/ubuntu/delete_log.sh
cs

(3) cron 재시작

1
service cron restart
cs

 

4. 부록 - cron 실행시간 이상

4-1. docker에서

위에 crontab 편집으로 입력한 정보에 따르면 매일밤 23:55에 delete_log.sh 파일이 실행 되어야 한다.

나의 경우 원래 머신의 시간은 런던시간이고, 최초에 서버를 세팅할때 타임존을 한국시간으로 변경하여 사용중이다.

또한 다른 이유로 인해 docker의 시간 역시 한국의 타임존으로 설정해 사용하고 있다.

그런데 cron 실행시간이 런던시간으로 실행되는 것을 확인했다.

검색해 나온 정보들을 적용해 보려했지만 난 잘되지 않았다.

어떻게 해야하나 고민을 했는데 사실 생각해보면 단순하게 해결이 가능하다.

특별한 관리상의 이유가 있는게 아니라면 crontab 편집에서 23시라고 입력한 것을 14시로 입력하면 되는거다.

4-2. docker 없이 ubuntu에서

모종의 이유로 인해 다시 docker의 사용을 멈추고, ubuntu에 tomcat을 사용하고 있으며,

위 설정대로 적용하고, 23:55에 실행되도록 적용했다.

그런데 놀랍게도 이건 또 내가 원하는 한국시간으로 적용되고 있다.

 

* tomcat 관련글

[tomcat] memory leak 에러 : https://deonggi.tistory.com/26
[Tomcat] 실행에러 : https://deonggi.tistory.com/76
[Tomcat] java.lang.IllegalArgumentException... : https://deonggi.tistory.com/77
[tomcat] war 배포하기, 가상 디렉토리 : https://deonggi.tistory.com/126
docker 컨테이너로 tomcat 실행하기 : https://deonggi.tistory.com/159

 

* 참고한 글

 

[Tomcat] catalina.out 파일 날짜 별 백업 및 오래된 로그파일 삭제 (ShellScript, Crontab)

1. Shell Script 파일 생성 [root@hostname ~]# cd /home [root@hostname home]# vi delete_log.sh 2. delete_log.sh에 아래 내용 입력(위치는 예시임) #톰캣 로그 디렉토리 TOMCAT_LOG=/home/tomcat/logs #오늘..

page-view.tistory.com

 

Crontab 설치 및 사용법

Crontab installation and Usage Crontab 설치 - CentOS # cron 설치 sudo yum update -y sudo yum install -y cronie # cron 시작 sudo systemctl start crond # cron systemctl 활성화 sudo systemctl enable cr..

blog.secuof.net

 

728x90
반응형
728x90
반응형

최근 좀 길게 웹 크롤링에 대해 작업을 했다.
넓은 범위로 크롤링 중인지라 길어지게 되었고, 어느정도 정리가 되어가고 있어 내용을 정리해 보고자 한다.

참고로 나는 docker에 ubuntu + tomcat + selenium을 사용중이다.
그러므로 docker 환경에 대한 내용이 필요하다면 이전 글을 참고하시길 바란다.
* docker에 대한 이전 글
1. docker 컨테이너로 tomcat 실행하기 : https://deonggi.tistory.com/159
2. docker 컨테이너의 환경설정 : https://deonggi.tistory.com/160

* 환경정보
java 1.8
spring boot 2.4.3
기본 OS : ubuntu 20.04.4 LTS
docker 내 세팅: ubuntu focal, tomcat 8.5.77, selenium-java 3.141.59

1. selenium을 사용하기로 했다.
처음에는 Jsoup을 이용하려고 했다.
기본적인 테스트도 됐지만 조금 더 검색해보니 selenium에 대한 언급이 많았고,
동적으로 작동하는 사이트에 알맞다는 내용들이 있어서 바꾸기로 했다.

 

[크롤링] Selenium을 이용한 JAVA 크롤러 (2) - Jsoup과 비교 (With. Twitter)

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 2020/02/27 - [Back-end/JAVA]..

heodolf.tistory.com


2. 브라우저를 선택한다.
브라우저에 랜더링 되는 내용을 긁어오기 때문에 서버환경에 브라우저와 함께
해당 브라우저의 webdriver가 설치되어야 한다.
당연히 나는 Chrome을 사용하기로 했다. 국내에서도 많이 사용하고 나도 익숙하니 Chrome이 좋다고 생각했다

그러나 이 결정은 서버에 배포하게 되면서 바꿔야만 했다. 내 서버는 Chrome을 설치할 수 없었기 때문이다.
그러므로 나와 같이 당황하고 싶지 않다면
먼저 당신의 서버에 사용하고자 하는 브라우저를 설치할 수 있는지 확인을 해봐야 한다.

나의 서버는 ubuntu를 사용하고 있다. (로컬환경은 windows 10. 나는 역시 한국사람 ㅎ)
Chrome을 사용하고 싶다면 서버가 36비트인지 64비트인지 확인해라.
64비트라면 설치가 가능할 것이다. (36비트여도 설치가 가능하다고 하는데 우선 나는 안 됐으니 알수 없고...)
내 경우 아키텍쳐가 aarch64이다.
ubuntu aarch64에 Chrome 설치를 검색해 봤더니 나오는 건 그냥 Chromium을 설치하라는 것이다.

 

Install Chrome on ubuntu/debian with arm64

I'm trying to install chrome using the commands below: wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - sh -c 'echo "deb [arch=$(dpkg --print-architecture)] h...

askubuntu.com

(그래도 집착을 버리지 못하고 Chrome을 설치하기 위해 많은 시도를 했지만 안 됐다.
만약 aarch64 아키텍쳐라면 고생하지 마시고 갈아 타시길...)
하지만 익숙하지 않은 Chromium이 어떨지 판단이 안되서 ubuntu 서버에 설치되어 있던 firefox를 사용하기로 했다.

3. 이제 코딩을 해보자.
firefox를 사용하기로 했으니 당연히 firefox 브라우저를 설치해야 한다.

1) 그리고 https://github.com/mozilla/geckodriver/releases 에서 브라우저 버전에 맞는 webdriver를 받아준다.
현재 내 PC에 설치된 firefox의 버전은 98.0.2 이다. 그리고 다운로드한 webdriver는 0.30.0 버전이다.


2) 다운로드한 webdriver의 압출을 해제하고 PC에 적당한 위치에 위치시켜 준다.


3) 크롤링 소스는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.TimeUnit;
 
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
 
public class Selenium {
 
    public static void main(String[] args) {
        try {
            String URL = "https://www.tistory.com/category/life";
            runSelenium(URL);
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    
    private static void runSelenium(String URL) throws Exception {
        System.out.println("#### START ####");
        
        // 1. WebDriver 경로 설정
        Path path = Paths.get("C:\\java/driver/geckodriver.exe");
        System.setProperty("webdriver.gecko.driver", path.toString());
        
        // 2. WebDriver 옵션 설정
        FirefoxOptions options = new FirefoxOptions();
        options.addArguments("--start-maximized");          // 최대크기로
        options.addArguments("--headless");                 // Browser를 띄우지 않음
        options.addArguments("--disable-gpu");              // GPU를 사용하지 않음, Linux에서 headless를 사용하는 경우 필요함.
        options.addArguments("--no-sandbox");               // Sandbox 프로세스를 사용하지 않음, Linux에서 headless를 사용하는 경우 필요함.
        options.addArguments("--disable-popup-blocking");    // 팝업 무시
        options.addArguments("--disable-default-apps");     // 기본앱 사용안함
        
        // 3. WebDriver 객체 생성
        WebDriver driver = new FirefoxDriver( options );
                
        try {
            
            // 4. 웹페이지 요청
            driver.get(URL);
            
            // 5. 페이지 로딩을 위한 최대 5초 대기
            driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
            
            // 6. 조회, 로드될 때까지 최대 10초 대기
            WebDriverWait wait = new WebDriverWait(driver, 10);
            String byFunKey = "CSSSELECTOR";
            String selectString = "div#mArticle";
//            String byFunKey = "XPATH";
//            String selectString = "//*[@id=\"mArticle\"]/div[2]/ul/li[3]/a";
            WebElement parent = wait.until(ExpectedConditions.presenceOfElementLocated( 
                    byFunKey.equals("XPATH") ? By.xpath(selectString) : By.cssSelector(selectString) ));
//            System.out.println("#### innerHTML : \n" + parent.getAttribute("innerHTML"));
            
            // 7. 콘텐츠 조회
            List<WebElement> bestContests = parent.findElements(By.cssSelector("div.section_best > ul > li > a"));
            System.out.println"best 콘텐츠 수 : " + bestContests.size() );
            if (bestContests.size() > 0) {
                for (WebElement best : bestContests) {
                    String title = best.findElement(By.cssSelector("div.wrap_cont > strong > span")).getText();
                    String name = best.findElement(By.cssSelector("div.info_g > span.txt_id")).getText();
                    System.out.println("Best title / blog name : " + title + " / " + name);
                    System.out.println("Best url : " + best.getAttribute("href"));
                    System.out.println();
                }
            }
            
            System.out.println("########################################");
            
            List<WebElement> contents = parent.findElements(By.cssSelector("div.section_list > ul > li > a"));
            System.out.println"조회된 콘텐츠 수 : " + contents.size() );
            if( contents.size() > 0 ) {
                for(WebElement content : contents ) {
                    String title = content.findElement(By.cssSelector("div.wrap_cont > strong > span")).getText();
                    String name = content.findElement(By.cssSelector("div.info_g > span.txt_id")).getText();
                    System.out.println("콘텐츠 title / blog name : " + title + " / " + name);
                    System.out.println("콘텐츠 url : " + content.getAttribute("href"));
                    System.out.println();
                }
            }
            
        } catch ( TimeoutException e ) {
            e.printStackTrace();
            System.out.println(e.toString());            
        } catch ( Exception e ) {
            e.printStackTrace();
            System.out.println(e.toString());         
        }
        
        // 8. WebDriver 종료
        driver.quit();
        
        System.out.println("#### END ####");
    }
}
 
cs

이 위 소스 TimeoutException 이 일어 날 수 있는 곳은 49, 60번 라인이니 참고하시길.

(1) 2)에서 위치시킨 webdriver 파일을 31라인에 기록하고 32번에서 해당 webdriver의 키를 적어준다.

(2) 60번 라인에 3항 연산자를 사용중인데, 당신이 태그를 어떻게 선택할 지에 따라 결정하여 사용하면 된다.
이외에 태그명, 클래스명 등 여러 선택방법이 있지만, 위 2가지 방법이 좀더 범용성이 있다.
그래서 나는 상황에 따라 내가 이해하고 사용하기 편한 것을 선택해서 사용 중이다.

 

(3) 100번 라인의 driver.quit()는 꼭 해줘야 한다. 만약 누락하는 경우 아래와 같이 서버가 다운되는 상황을 맞이할 수 있다.

 

만약 selectString에 해당되는 값을 찾는 것이 어렵다면 아래와 같은 방법으로 얻을 수 있다.
① Chrome 브라우저에서 개발자 모드를 연다. (F12)
② Elements에서 커서 모양을 선택(단축키 Ctrl+Shift+C) 하고 화면에서 원하는 태그를 선택한다.
③ Elements에서 선택되어진 태그에서 마우스 우클릭 -> Copy -> 원하는 항목 선택한다.


(3) 크롤링에서 얻고자 하는 것이 태그는 아닐 것이다.
태그에는 여러 정보가 있으므로, 원하는 것이 텍스트인지 링크인지 등을 판단하고 추출해야 한다.
(69번, 72번 라인 소스를 참조)

(4) 그래서 해당 코드의 전체 구조를 간략히 설명하면 아래와 같다.
① 드라이버와 옵션을 선택하고 URL의 페이지를 로딩한다.
② 페이지에서 크롤링 대상 영역의 큰 부분을 먼저 크롤링 한다. (60번 라인)
③ 세부 콘텐츠를 크롤링 한다. (65 ~ 89번 라인)

(4) 실행결과 (크롤링 자체 또는 브라우저 로그는 제거함)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#### START ####
best 콘텐츠 수 : 3
Best title / blog name : 뭐 어쩌라고!? 감자가 감자한 오늘. / 만두감자 랜선집사 in 일본
Best url : https://dumplingj.tistory.com/1048
 
Best title / blog name : 순대볶음 레시피, 들깻가루 이젠 더 이상 필요없어요 / 미자하우스
Best url : https://mija0515.tistory.com/52
 
Best title / blog name : 강아지 중성화 수술 시기, 장단점 모두 알아보고 수술여부 결정하자! / 곤냥마마
Best url : https://gonnyangmama.tistory.com/829
 
########################################
조회된 콘텐츠 수 : 9
콘텐츠 title / blog name : 뿔논병아리 육아 일기 / 행복한 사진 이야기
콘텐츠 url : https://dslrclub.tistory.com/254
 
콘텐츠 title / blog name : 2022년 기본형 공익직불금 신청하기 / 연서의여행
콘텐츠 url : https://lover6126.tistory.com/46
 
콘텐츠 title / blog name : [집밥] 초간단 해장용 된장라면밥 만들기 / 밥집(Bapzip)
콘텐츠 url : https://babzip.tistory.com/1571
 
콘텐츠 title / blog name : [걷기]두루누비 '2022 코리아둘레길 원정대' 및 걷기여행작가 모집해요. / 라온PD와 함께 디지털 노마드
콘텐츠 url : https://raonpd.tistory.com/56
 
콘텐츠 title / blog name : 송도 해수욕장 / 제이제로 다정한 일상
콘텐츠 url : https://wpdlwpfh.tistory.com/330
 
콘텐츠 title / blog name : 떼아와 함께한 커피 타임과 오페라 케이크 / 밀리멜리
콘텐츠 url : https://milymely.tistory.com/570
 
콘텐츠 title / blog name : 딸기쏙우유 찹쌀떡 리뷰 / 현토리의 일상
콘텐츠 url : https://hyuntori83.tistory.com/29
 
콘텐츠 title / blog name : 고창 / stealthily
콘텐츠 url : https://waterpolo.tistory.com/43
 
콘텐츠 title / blog name : 앱테크 추천 개이득과 유사한 출석체크형 앱테크 앱 모음 & 총정리 (1인 개발자 mrtest) / 생활의꿀팁tv
콘텐츠 url : https://livehoneytv.tistory.com/121
#### END ####
cs


(5) 부록 - 스크롤 다운 (수정)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Selenium {
 
    /* 생략 */
    
    private static final Integer WAIT_SEC = 1;
    private static final Integer SCROLL_COUNT = 2;
    
    private static void scrolldonw(WebDriver driver) throws InterruptedException {
//        driver.manage().timeouts().implicitlyWait(WAIT_SEC, TimeUnit.SECONDS);
        Thread.sleep(WAIT_SEC*1000);
        if (SCROLL_COUNT > 0) {            
            for (int i=0; i<SCROLL_COUNT; i++) {
                JavascriptExecutor JavascriptExecutor = ((JavascriptExecutor)driver);
                Long beforeHeight = (Long) JavascriptExecutor.executeScript("return document.body.scrollHeight;");
                System.out.println("브라우저 높이 (before) : " + beforeHeight);
                // 스크롤 다운 후가 전보다 커질때까지 스크롤 다운 반복
                Long afterHeight = 0L;
                while(beforeHeight >= afterHeight) {
//                    driver.manage().timeouts().implicitlyWait(WAIT_SEC, TimeUnit.SECONDS);
                    Thread.sleep(WAIT_SEC*1000);
                    JavascriptExecutor.executeScript("window.scrollTo(0, document.body.scrollHeight);");
                    afterHeight = (Long)JavascriptExecutor.executeScript("return document.body.scrollHeight;");
                    System.out.println("브라우저 높이 (after) : " + afterHeight);
                }
            }
        }
    }
}
 
cs

스크롤 다운 후 로딩되는 항목을 크롤링 해야할 때 유용하다. (5, 6번 라인은 상황에 맞게 결정)
첫번째 크롤링 소스의 52번 다음 라인에서 해당 함수의 호출을 추가하면 된다.

(1) 13, 14번 라인은 브라우저 높이를 구하기 위해 자바스크립트를 실행하는 코드다.
(2) 18~24번 라인은 스크롤 전후 높이 값을 비교하여 후 값이 큰 경우 정상 스크롤 된 것으로 판단하고 스크롤 된 카운트를 증가 시킨다.

(3) 추가 - 9, 19번 라인의 implicitlyWait는 페이지 로딩에 사용하는 것이라 적당하지 않아 Thread.sleep으로 변경했다.

* 참고한 글

 

[Java] 크롤링 crawling, 셀레니움 Selenium

[Java] 크롤링 crawling, 셀레니움 Selenium 웹 크롤링의 정식 명칭은 Web Scraping이며, 웹 사이트에서 원하는 정보를 추출하는 것을 의미한다. 보통 웹 사이트는 HTML기반이기 때문에 정보를 추출할 페이

heekng.tistory.com

 

[크롤링] Selenium을 이용한 JAVA 크롤러 (1) - HTML 파싱

2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 0. 서론  이전 포스트에서 Js..

heodolf.tistory.com

 

[Python] Selenium 웹페이지 스크롤하기 scrollTo, Scroll down

Python 의 selenium 을 이용해서 스크롤 하기 크롤링 할 때 웹페이지를 스크롤 다운해야하는 경우가 있죠. 스크롤다운해서 끝까지 가야 그 다음 데이터를 조회하는 경우가 있고 그 외에도 필요한 경

hello-bryan.tistory.com


4. 크롤링 소스 서버 배포 에러
처음 배포할때 멋도 모르고 로컬 windows에서 처럼 사이트에서 webdriver 파일을 받아 원하는 위치에 넣고
그 폴더의 파일을 지정해 주면 되는 줄 알았다. 이 착각이 내 큰 고난의 이유 중 하나다.

먼저 난 windows에서 처럼 webdriver 파일을 받아 원하는 위치에 넣었다.
서버는 돌아가는데 크롤링을 하면 아래와 같은 에러가 나온다.
* 참고 : /drivers/firefox/geckodriver 는 내가 webdriver 파일을 배치한 위치

1) gradle selenium-java 4.1.2 version 사용시

1
2
3
4
5
6
7
/drivers/firefox/geckodriver: 1: ELF: not found
/drivers/firefox/geckodriver: 2: Syntax error: ")" unexpected
20220317 17:47:13.083 [http-nio-8091-exec-6] ERROR c.d.b.c.a.ExceptionControllerAdvice - 
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/openqa/selenium/internal/Require
.
.
.
cs

2) gradle selenium-java 3.141.59 version 사용시

1
2
3
4
5
6
7
8
9
10
/drivers/firefox/geckodriver: 1: ELF: not found
/drivers/firefox/geckodriver: 2: Syntax error: ")" unexpected
20220317 17:59:14.320 [http-nio-8091-exec-6] ERROR c.d.b.c.a.ExceptionControllerAdvice - 
org.openqa.selenium.WebDriverException: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:3165
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:17:03'
System info: host: 'server02', ip: '127.0.1.1', os.name: 'Linux', os.arch: 'aarch64', os.version: '5.13.0-1021-oracle', java.version: '11.0.14'
Driver info: driver.version: FirefoxDriver
.
.
.
cs


구글을 검색해도 나오는 것이라곤 버전을 내려라. 버전을 브라우저에 맞춰라. 등등인데...
나의 경우는 근본적으로 버전 이전에 ubuntu에 firefox에 대한 webdriver를 설치하지 않았다는게 문제였다.
아주 우연히 거의 포기 직전에 찾은 정보인데 webdriver를 설치하고 위치를 지정하라는 것이다.

1
2
3
4
# firefox webdriver 설치
apt-get install firefox-geckodriver
 
# 설치된 webdriver 위치 : /usr/bin/geckodriver
cs

* 출처 : https://ubuntu.pkgs.org/20.04/ubuntu-updates-universe-arm64/firefox-geckodriver_99.0+build2-0ubuntu0.20.04.2_arm64.deb.html

그리고 배포하니 위 1)번의 nested exception is java.lang.NoClassDefFoundError: org/openqa/selenium/internal/Require 에러가 나시 나타났고, (1,2번 라인 에러 없이)
그동안의 검색으로 얻은 정보를 이용해 gradle selenium-java 3.141.59 버전으로 변경하니 크롤링이 실행 되었다.


이상으로 selenium을 사용하며 내가 겪었던 일들이다.
나도 전문가는 아니기에 여기 저기서 조각조각 모아 결국 반영한 내용이다.
부디 나 이후에 하실 분들은 나 같은 삽질을 안 하시길... 빌어본다. '') (먼산)

 

 

* 관련 글 : [java, Jsoup] 크롤링으로 본문 가져오기

 

 

728x90
반응형
728x90
반응형

이전글에서는 docker의 컨테이너 설치부터 삭제까지 간단히 다뤘다.

이번엔 내가 docker를 사용하면서 필요했던 설정에 대해서 적어보고자 한다.

 

이 글의 주요 내용은 아래와 같다.

1. 여러 컨테이너의 서버자원 사용을 관리하고 싶다.

2. 컨테이너는 host에 설치된 DB에 연결하고 싶다.

3. 컨테이너에서 업로드 또는 다운로드 되는 파일은 host 영역에 배치하고 싶다.

번외1. docker를 이용하며 발생한 이슈 : 서버시간, 한글

번외2. log파일을 관리해야 겠다.

번외3. docker file을 만든다.

 

1. 여러 컨테이너의 서버자원 사용을 관리하고 싶다.

내 운영서버는 1대로 dev, prod, 컨테이너 관리를 위한 portainer를 함께 사용한다.

이 말은 각 컨테이너의 자원을 할당해야 한다는 의미다.

아무래도 prod가 더 많은 일을 할 것이고, 문제없이 서비스 되어야 하므로 다른 컨테이너들이 자원을 사용하는 것을 제한해야 한다고 생각했다.

docker stats 명령어를 통해 현재 실행중인 docker 컨테이너의 자원 사용상황을 확인할 수 있다.

자원 할당전

 

나는 그래서 prod외 컨테이너들의 cpu, memory 각 10%로 제한하기로 했다.

(아래 방법은 실시간으로 변경하는 방법이다.)

docker update --memory 용량 --memory-swap 용량 컨테이너명 : 컨테이너의 메모리 사용은 2.4g로 제한했다.

docker update --cpus=갯수(또는 비율) 컨테이너명 : --cpus에 정수인 경우 CPU의 코어 갯수이고, 1 미만의 실수를 입력하면 비율로 정해진다.

 

이후 자원 사용현황을 확인하면 아래와 같다.

자원 할당후 (cpu 할당 변화는 어떻게 보는걸까?)

 

2. 컨테이너는 host에 설치된 DB에 연결하고 싶다.

docker 컨테이너의 개념에서 실제 운영중인 DB를 컨테이너로 관리한다는 것은 무리가 있다고 생각했다.

그래서 host의 설치된 DB를 각 컨테이너가 연결하는 것이 맞다고 생각하고, 연결하기로 했다.

그럼 내부에서의 IP만 알면 되는 것인데 이건 어떻게 확인할 것인가?

ifconfig를 입력하면 확인되는 docker0의 ip로 접근하면 된다.

 

만약 ifconfig를 사용할 수 없다면 아래 명령어로 패키지를 설치하도록.

sudo apt-get update

sudo apt install net-tools 

 

자세한 docker 네트워크에 대해서는 아래 글 참고

 

Docker 네트워크 이해 - Voyager of Linux

Table of Contents1 Docker 네크워크 상태2 Docker0 브릿지 네트워크3 Docker 네트워크 특징4 Docker Network Namespace5 docker0 와 eth0 의 연결: iptables -t nat6 Inter-Container Communication7 docker0 브릿지 네트워크 ip 변경 Docke

linux.systemv.pe.kr

 

3. 컨테이너에서 업로드 또는 다운로드 되는 파일은 host 영역에 배치하고 싶다.

나는 host에 존재하는 폴더를 컨테이너에서 읽기, 쓰기, 삭제의 모든 권한이 필요하다.

그래서 내가 사용한 것은 bind mount다. 

현재 권장되는 것은 아니나(docker에서는 volume mount를 권장),

난 내가 지정한 위치에 폴더를 배치하고 싶어서 사용하기로 했다.

 

아래 순서로 진행하면 컨테이너에서 host의 폴더를 mount되는 것을 확인할 수 있다.

1) host에 mount 할 폴더를 만들고 테스트로 txt파일을 하나 배치한다.

 

2) 컨테이너를 만들면서 bind mount 한다.

bind mount와 함께 컨테이너 생성

docker run -d -it --name 컨테이너명 -v host의폴더명:컨테이너의폴더명 -p 이미지명

 

3) 컨테이너로 접속해서 파일 확인

컨테이너에 접속해 보면 mount된 폴더가 확인된다. 그리고 파일 삭제도 가능하다.

이렇게 하면 컨테이너에서 host의 폴더를 내 폴더인 처럼 바로 사용이 가능하다.

 

docker의 데이터 파일 mount에 관련해서 자세한 내용은 아래 글 참조

 

Docker 컨테이너에 데이터 저장 (볼륨/바인드 마운트)

Engineering Blog by Dale Seo

www.daleseo.com

 

번외1. docker를 이용하며 발생한 이슈 : 서버시간, 한글

이후 운영서버에서 서비스 오픈하고 상황을 모니터링하는 과정에서 몇가지 이슈가 발견 되었다.

 

1) 서버시간이다.

내 서버는 특정 시간마다 진행되는 스케줄이 있다.

나의 운영서버는 세팅한지 1년이 넘어가는 서버로 이미 세팅할때 서버시간을 한국시간으로 적용하고 있었다.

그래서 당연히 정해진 시간에 정해진 스케줄이 돌아가리라 생각하고 있었다.

그런데 실행되는 스케줄이 내가 정해 놓은 시간과 다르다.

이러한 이유는 컨테이너의 시간이 운영서버의 시간과 다를 수 있기 때문이다.

(사실 이 결론에 도달하는데 여러 삽질이 있긴 했지만 그건 그냥 넘어가기로 하고....)

 

컨테이너로 접속하고 아래 순서로 진행해 보자.

apt-get update && apt-get -y upgrade : tzdata을 사용할 수 없는 경우
apt-get -y install tzdata : 타임존 설정
6(Asia) -> 69(Seoul) 선택

 

그리고 터미널에 date를 입력하면 컨테이너가 적용되고 있는 시간이 변경된 것을 확인할 수 있다.

 

시간 변경에 대해서는 아래글 참조

 

docker container 컨테이너 시간 변경 방법

docker container 컨테이너 시간 변경 방법 docker에 Tomcat을 올려 사용하던 중 로그 시간이 안맞아 보기 불편하여 정리. docker 컨테이너 시간바꾸기 -컨테이너 접속 후 # dpkg-reconfigure tzdata - 한국 서울..

tptrmfwk.tistory.com

 

[solved] ubuntu 도커 이미지 빌드 시 timezone 설정 방법

최근 Docker를 이용한 작업이 많아지면서 여러 이슈들이 있었는데, 그 중에서 많은 삽질을 했던 도커 컨테이너 시간대 설정 방법을 공유한다.

stynxh.github.io

 

2) 한글이 안 나온다.

가끔 서버에서 log 메시지를 기록할 때가 있다. 근데 문제는 이게 깨져 나온다는 거다.

컨테이너 내부에서 변경하는 방법도 있긴하나 나는 컨테이너 생성시 지정하는 것을 선호한다.

docker run -d -it -e LC_ALL=C.UTF-8 --name 컨테이너명 이미지명

 

언어변경에 대해서는 아래 글 참조

 

도커(Docker) 컨테이너 로케일 설정: 데비안(Debian), 우분투(Ubuntu) 이미지에서 한글 입력 문제

도커 우분투, 데비안 이미지에서는 기본적으로 한글 입력을 지원하지 않습니다. 한글을 입력하기 위해서는 로케일 설정을 UTF-8로 지정할 필요가 있습니다. 이 글에서는 로케일 개념과 함께 도커

www.44bits.io

 

번외2. log파일을 관리해야 겠다.

내 운영서버는 무료 클라우드 서버다. 그래서 디스크 잔여 용량에 대해서 신경 쓰고 있다.

그래서 무한대로 늘어날 가능성이 있지만 매번 관리하기 어려운 log는 관리 대상인 셈이다.

 

이 내용은 단순하지 않아서 자세한 글을 링크해 둔다. 이 글을 참고해서 적용해 보길 바란다.

 

Logrotate와 Crontab을 활용한 Docker Log 관리

💡늘어나는 Docker log는 어떻게 관리할까??🤷‍♀️

velog.io

+ 덧

이걸로 완전히 해결되지는 않는다. 쓰다보면 점점 docker에 대한 파일 용량이 늘어난다.

그리고 뭔가 관리로 해결할 방법을 찾아봤는데 쉽지가 않다.

다행이라면 docker는 쉽게 설치, 삭제를 가능하게 하는 것이니 완전히 삭제하는 방법이 그나마 내가 찾은 차선책 이었다.

 

 

Cloud VM에 docker를 적용 후 sda 용량 100% 문제

Cloud VM에 docker를 적용 후 sda 용량 100% 문제 오늘 테스트를 하면서 해결한 듯 하면서도 찜찜하다. 이유는 시험한 내용을 설명 후 쓰도록 하겠다. 전반적인 내용을 확인하고자 하시는 분은 htt

shyash.tistory.com

 

번외3. docker file을 만든다.

docker file은 나만의 여러 환경설정이 지정되어 있는 docker 이미지를 만드는데 사용된다.

그러므로 동일한 환경을 자주 구성해야 한다면, 그리고 그 환경이 여러가지를 설정을 포함하고 있다면 docker file을 만드는 것을 추천한다.

 

역시 이 내용도 자세한 글을 링크해 두니 참고하시길.

 

emunhi

Programming > Docker [Tomcat] Dockerfile 로 설치하기 1) Dockerfile 만들기 ※  $ vi Dockerfile 설치할려는 tomcat버전은 바뀔수가 있으므로 tomcat 홈페이지에서 검색해서 사용하면 된다. ※ 내역 /usr/local/tomcat/webap

emunhi.com

 

 

 

728x90
반응형
728x90
반응형

이번에 내가 docker을 적용하면서 겪은 내용을 정리해 본다.
먼저 docker가 무엇이고 왜 사용하는지는 구글을 검색해보길 바란다.

ubuntu 서버 세팅은 아래 내 글을 참고하자.

[Oracle Cloud] ubuntu server setting

지난주 금요일 오후에 클라우드 서버에 수정된 내용을 반영하려고 보니 접속이 되지 않았다. 분명 전날까지 됐던것 같은데 안 되는 것이다. 무료 평가판이 종료되었다는 메일은 받았지만, 그래

deonggi.tistory.com


1. docker 설치 및 세팅은 이 글을 참고

Ubuntu 20.04 Docker 설치하기. - 달소씨의 하루

이번에는 Ubuntu 20.04 LTS Server 버전에서 Docker 설치를 한번 진행해보겠습니다. 설치방법은 매번 하던대로라서.. 크게 달라지지않지만 기록용으로 남겨봅니다. 설치하기전에 기본적으로 apt update & ap

blog.dalso.org

이 글을 따라면 docker를 설치할 수 있다.
그리고 해당글의 portainer는 docker의 이미지, 컨테이너, 네트워크 관리에서 유용하므로 설치하는 것을 추천한다.

2. 원하는 tomcat 이미지를 받자.
docker hub에 접속하여 원하는 이미지를 검색한다.
그리고 docker pull 이미지명 으로 이미지를 받는다.
이미지 명의 콜론(:) 버전을 구분하기 위한 태그다. 원하는 특정 버전을 설치한다면 태그를 입력해 줘야 한다.

docker images로 현재 가지고 있는 이미지를 확인할 수 있다.

3. 이미지로 컨테이너 생성 및 실행

docker run -d -it --name con_test -p 8080:8080 tomcat:8.5.78-jre8-temurin-focal
입력된 명령어를 정리하면
1) -d : 백그라운드로 실행, 터미널을 빠져나와도 여전히 컨테이너는 실행됨
2) -it : 컨테이너를 종료하지 않고 계속 터미널에 입력
3) --name : 컨테이너의 이름을 지정 (길어 잘렸는데 위 스샷에서 맨아래 붉은줄에서 오른쪽 끝에 해당 이름이 표시됨)
4) -p : 포트지정, docker host 포트:docker 컨테이너 내부 포트로 바인딩
외부에서 8080포트로 서버에 접근하면 컨테이너 내부의 톰캣 기본 포트인 8080으로 연결해 주겠다는 의미
5) 맨 뒤의 tomcat:8.5.78-jre8-temurin-focal 은 2번에서 받은 이미지로 컨테이너를 생성

4. 톰캣 실행 결과
위 4번에서 두번째 빨간줄 docker ps -a 명령어로 이미 실행중임이 확인되고 있다.
그런데 브라우저로 접속하면 아래와 같다.

404 에러가 나고 있는데 이는 톰캣의 응답으로 정상이다.
call에 응답할 있는 war 파일을 넣어주면 된다
그러려면 설치된 컨테이너에 war 파일을 어디에 위치하고, 어떻게 넣어줘야 할까?

명령어를 docker exec -it con_test bash 라고 입력해준다.
con_test 컨테이너로 접속하여 명령어를 입력하겠다는 의미다.
붉은색 박스를 보면 컨테이너로 접속된 것을 알 수 있다.
컨테이너에 접속된 위치가 tomcat이 설치된 것으로 보여지는데 ls -la로 확인하면 맞는것을 알 수 있다.
그러므로 con_test 컨테이너의 /usr/local/tomcat/webapps 폴더에 ROOT.war 라고 war 파일을 넣어주면 되는 것이다.

exit로 컨테이너에서 나오고, docker 컨테이너에 파일을 복사해 넣는다.
docker cp /opt/HelloWorld.war con_test:/usr/local/tomcat/webapps/ROOT.war
docker host의 /opt 폴더에 HelloWorld.war 파일이 있는데 이걸 con_test 컨테이너의 /usr/local/tomcat/webapps 폴더에 ROOT.war로 파일로 넣어준다는 의미다.
에러 없이 복사 되었는지 확인해 준다. 파일명이 잘못된거나 폴더가 잘못 되었다면 에러가 난다.
(반대로 하면 컨테이너 파일을 docker host로 복사할 수 있다.)

그리고 잠시 후 다시 8080 포트로 접속해 보면 아래와 같이 정상 적용되는 것을 확인할 수 있다.


테스트를 위해 간단히 만든 war 파일로 Hello World라고 표시하고 있다.

5. 톰캣 컨테이너 삭제

docker stop con_test (또는 container id) : 컨테이너를 정지
docker rm con_test (또는 container id) : 컨테이너 삭제

6. docker 이미지 삭제

docker rmi tomcat:8.5.78-jre8-temurin-focal (또는 image id)


728x90
반응형
728x90
반응형

mysql에 데이터를 insert하는 과정에서 발생된 에러다.

java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x99 "...' for column 'DSC' at row 1

 

원인은 '😙'와 같은 이모지를 기존에 사용하던 DB에 insert하려 했다는 문제다.

웹에서 검색한 대부분은 답변은 character set을 변경하라는 내용이다.

 

MySQL utf8에서 utf8mb4로 마이그레이션 하기

Emoji같은 글자들은 utf8 인코딩 되는경우 글자당 최대 4bytes까지 필요하다. 하지만 기존 MySQL의 utf8 필드의 경우 글자당 최대 3bytes 까지만 지원하는 한계점이 있었다. 때문에 MySql database에서 utf8mb4

www.letmecompile.com

 

물론 변경하는 것이 가장 베스트의 선택이다. 

그러나 나는 이미 운영중인 서버이기 때문에 함부로 선택할 수 없는 문제였고,

이런 이모지가 꼭 필요한 요소인가를 봤을때 아니라고 생각했다.

그래서 이모지를 제거 또는 변환하는 방법을 사용하기로 했다.

 

 

[Java] Emoji 문자 제거

현재 웹에서 제공하는 이모지 [ win + . ] 또는 모바일 단말기에서 제공하는 특수문자들이 유니코드이지만 DB에 저장 시 지원하지 않는 경우 에러를 유발하게 됩니다. 😊 정확하게는 uft8mb4를 지원

string.tistory.com

 

이렇게 문제가 해결되는 줄 알았다. (분명 로컬에서는 잘 돌았거든..)

그런데 tomcat 서버에서 실행될 때 문제가 발생 했다.

좀 큰 덩어리의 스케줄로 돌아가는 부분이기 때문에 자세한 검토는 하지 못했지만

짐작하기로는 해당 라이브러리가 내 운영서버 환경에서는 적용되지 않는 것이 아닌가 짐작하고 있다. 

아쉽지만 운영서버에서 발생되는 문제의 원인은 아직 확인하지 못했다.

다만, 항상 발생되는 문제가 아니고, 중요한 요소가 아닌지라 이것으로 인해 에러가 발생되면 패스하도록

예외처리를 통해 피하고 있는 중이다.

728x90
반응형

+ Recent posts