Archive for : 5월, 2018

[Angular] Apache 서버로 Angular 앱을 배포했을때 404 에러가 날 경우

SPA(Single Page Application)을 Apache서버를 통해 서비스를 할 경우 주의해야 할 점이 있다.

루트 url을 제외한 url을 페이지를 새로고침하거나 브라우저에 직접 입력해서 접근하는 경우 404에러를 접하게 된다.

비단 Angular로 만들어진 앱 뿐만아니라 React나 Vue로 만들어진 앱도 마찬가지일 것이다. (HashLocation strategy를 사용할 경우는 예외)

그도 그럴것이 우리가 입력하는 url의 경우 Apache에 전달되게 되는데 Apache에는 해당 리소스들이 없기 때문이다. 해결 방법은 모든 url request를 index.html로 가게 만들어 주면 된다.

SPA가 있는 디렉토리에 .htaccess 파일을 만들어 다음과 같이 작성해 준다.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !/api
  RewriteRule . /index.html [L]
</IfModule>

그리고 Apache의 rewrite 모듈을 활성화 해주고 Apache설정에 다음과 같이 SPA디렉토리 경로에 대해 설정을 추가해 준다.

<Directory "/path-to-spa">
  AllowOverride All
</Directory>

자세한 내용은 아래 링크를 참고한다.

https://github.com/mgechev/angular-seed/wiki/Deploying-prod-build-to-Apache-2

[Spring] Spring Boot fully executable jar 로 배포할때 주의사항

스프링 부트는 war 또는 jar 로 배포 가능하다.

jar의 경우 톰캣을 내장하여 jar파일 하나로 서비스가 가능하다. 게다가 fully executable application으로 만드는 것도 가능하다. 다른 executable binary 처럼 리눅스의 init.d나 systemd에 서비스로 등록되어 제어가능하다는 이야기이다. fully executable jar로 만들기 위해서는 maven이나 gradle 설정파일에 설정을 추가해주면 된다.

maven의 경우,

<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<executable>true</executable>
	</configuration>
</plugin>

를 pom.xml에 추가해준다.

gradle의 경우,

springBoot {
    executable = true
}

를 build.gradle 파일에 추가해 준다. 참고로 Spring boot 2.0버전부터는 위 설정 대신 아래의 설정으로 대체된 것 같다.

bootJar {
    launchScript()
}

자세한 방법은 아래 공식 문서에 자세히 나와있다.

https://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html

스프링 부트를 이용한 배포는 매우 편리하다. Micro Service Architecture를 위해 만들어진게 아닌가 싶다. 이렇게 편리한 스프링 부트이지만 실제 서비스에 배포할때 몇가지 삽질했던 부분이 있어 글로 남긴다.

먼저 로컬에서 실행할때는 encoding이 utf-8로 잘 적용되어 로그가 정상적으로 남았는데 실제 리눅스(centos) 에 배포했을때에는 아래와 같이 한글로그가 깨졌다.

문제는 리눅스의 systemd(service)에 있었다. 아래 스택오버플로우 글을 참고하자.

https://stackoverflow.com/questions/39163590/how-to-set-utf-8-character-encoding-in-spring-boot

해결책은 jar파일이 있는 경로에 jar파일과 같은 이름으로 .conf 파일을 만들고 아래 한줄을 추가해주면 된다.

export LANG='ko_KR.UTF-8'

또 한가지 겪었던 이슈는 파일 생성이다.

The temporary upload location [/tmp/tomcat.1569213510584817208.8080/work/Tomcat/localhost/ROOT] is not valid

위와 같은 에러가 뜨면서 Spring application 내에서 파일 생성이 안되었다.  아래와 같이 application.yml에 embeded tomcat의 basedir을 설정해 주어 해결되었다.

server:
  tomcat:
    basedir: /data/tomcat-base

자세한 내용은 아래 링크에 설명되어 있다.

https://tridion.stackexchange.com/questions/14324/temporary-upload-location-is-not-valid/16831

배포 방식이 달라  생기는 이슈들이 좀 있기는 하지만 그래도 스프링 부트는 장점이 어마무시하다.

누군가의 글에서 읽은 아래 글귀가 너무 공감이 간다.

스프링 부트는 사랑입니다.

[logstash] Mysql DB에서 입력받아 Elasticsearch로 보내기

logstash는 실시간 파이프라인 기능을 가진 오픈소스 데이터 수집 엔진이다.

자세한 설명은 아래의 공식 문서를 참조하자.

https://www.elastic.co/guide/en/logstash/current/introduction.html

간단히 말해서 데이터를 수집 해서 원하는 형태로 가공하여 출력해주는 도구이다.

따라서 입력과 필터 그리고 출력을 어떤형태로 할것인지 기술해놓은 아래와 같은 설정파일이 필요하다.

# This is a comment. You should use comments to describe
# parts of your configuration.
input {
  ...
}

filter {
  ...
}

output {
  ...
}

위의 설정 중에 입력과 출력은 필수이며 필터는 옵션이다.

logstash는 다양한 입출력을 plugin을 통해 지원한다.

플러그인을 따로 설치해줄 필요는 없으며 설정파일에 기술하는 것으로 사용이 가능하다.

주로 아파치 같은 웹서버의 로그를 분석하는데에 많이 쓰이는 것 같다.

내 경우엔 Mysql DB에서 데이터를 뽑아 elasticsearch에 넣어줄 일이 생겨 필요한 설정들을 모아봤다.

우선 Input plugin으로 JDBC 플러그인 설정을 아래와 같이 해주었다.

input {
  jdbc {
    jdbc_driver_library => "{path_to_driver_file}/mysql-connector-java-5.1.46-bin.jar"
    jdbc_driver_class => "com.mysql.jdbc.driver"
    jdbc_connection_string => "jdbc:mysql://{database_host}/{database}"
    jdbc_user => "{username}"
    jdbc_password => "{password}"
    statement_filepath => "{path_to_sql}/song.sql"
  }
}

그 다음 Output plugin으로 elasticsearch 설정을 아래와 같이 해주었다.

output {
  elasticsearch {
    hosts => "localhost:9200"
    index => "{index}"
    codec => "json"
    document_type => "{document_type}"
    document_id => "%{idx}"
  }
}

위의 설정들로 끝났으면 좋았겠지만 아쉽게도 문제가 있었다.

sql로 가져와야 하는 데이터는 join을 해서 가져오는 데이터였고 그렇기 때문에 elasticsearch에 하나의 index로 넣어주기 위해서는 데이터를 합쳐주는 작업이 필요했다.

즉 쿼리로 가져온 아래와 같은 데이터를

아래의 포맷으로 합쳐주는 작업이 필요했다.

 

해당 작업은 Filter plugin 중 하나인 aggregate plugin 을 사용하였다.

filter {
  aggregate {
    task_id => "%{idx}"
    code => "
      map['idx'] = event.get('idx')
      map['songName'] = event.get('song_name')
      map['singerName'] = event.get('singer_name')
      map['regTime'] = event.get('reg_time')

      map['songNames'] ||= []

      if !(map['songNames'].include? event.get('song_name') )
        map['songNames'] << event.get('song_name')
      end

      map['singerNames'] ||= []

      if !(map['singerNames'].include? event.get('singer_name') )
        map['singerNames'] << event.get('singer_name')
      end

      event.cancel()
    "
    push_previous_map_as_event => true
    timeout => 5
  }
  mutate {
    remove_field => ["songName","singerName"]
  }
}

어느 언어의 문법을 차용한 것인지는 모르겠다. 그냥 구글링해서 찾아 내 경우에 맞게 수정한 것이다.

참고로 ||= 이부분이 새 array를 만드는 문법인거같은데 ||    = 이렇게 두 문자 사이에 공백이 있으면 에러가 난다.

혹시 어디에서 나온 문법인지 아시는분 댓글좀 부탁드립니다. 꾸벅

그리하여 아래와 같은 최종 설정파일이 만들어졌다.

input {
  jdbc {
    jdbc_driver_library => "{path_to_driver_file}/mysql-connector-java-5.1.46-bin.jar"
    jdbc_driver_class => "com.mysql.jdbc.driver"
    jdbc_connection_string => "jdbc:mysql://{database_host}/{database}"
    jdbc_user => "{username}"
    jdbc_password => "{password}"
    statement_filepath => "{path_to_sql}/song.sql"
  }
}

filter {
  aggregate {
    task_id => "%{idx}"
    code => "
      map['idx'] = event.get('idx')
      map['songName'] = event.get('song_name')
      map['singerName'] = event.get('singer_name')
      map['regTime'] = event.get('reg_time')

      map['songNames'] ||= []

      if !(map['songNames'].include? event.get('song_name') )
        map['songNames'] << event.get('song_name')
      end

      map['singerNames'] ||= []

      if !(map['singerNames'].include? event.get('singer_name') )
        map['singerNames'] << event.get('singer_name')
      end

      event.cancel()
    "
    push_previous_map_as_event => true
    timeout => 5
  }
  mutate {
    remove_field => ["songName","singerName"]
  }
}

output {
  elasticsearch {
    hosts => "localhost:9200"
    index => "{index}"
    codec => "json"
    document_type => "{document_type}"
    document_id => "%{idx}"
  }
}

위와 같이 작성된 설정파일을 가지고 logstash -f /config-dir-path/final.conf 를 실행해 주니 원하는 대로 데이터를 잘 옮겨 준다.