git clone https://github.com/deviantony/docker-elk

nginx를 설치하고 docker 기반으로 ELK (elasticsearch, logstash, kibana)를 설치하고 nginx 로그를 filebeat를 설치하여 acces.log, error.log, syslog등을 전송해보자.

 

설치 

ELK를 도커에 설치하는 스크립트를 아래 github에 잘 정리되어 제공해주고 있다.
https://github.com/deviantony/docker-elk

ELK는 이걸로 설치하면 되는데 docker-compose로 nginx와 filebeat까지 함께 설치하기 위해서 아래 저장소에서 제공하는 nginx-filebeat 스크립트를 혼합해서 사용해보자.
https://github.com/spujadas/elk-docker/tree/master/nginx-filebeat

1. 우선 ELK 설치 스크립트를 가져오자.

git clone https://github.com/deviantony/docker-elk



2. 그리고 nginx-filebeat 파일을 다운 받아서 docker-elk 디렉토리 내부에 추가한다.

git clone https://github.com/spujadas/elk-docker
mv ./elk-docker/nginx-filebeat ./docker-elk



3. 그리고 nginx-filebeat까지 사용할 수 있도록 docker-compose.yml 스크립트를 수정해준다. 그리고 nginx.conf 파일을 쉽게 보고 생성된 log도 로컬에서 보기 위해서 mount를 로컬 폴더로 진행한다.

version: '3.2'

services:
  elasticsearch:
    container_name: "elasticsearch"
    build:
      context: elasticsearch/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./elasticsearch/config/elasticsearch.yml
        target: /usr/share/elasticsearch/config/elasticsearch.yml
        read_only: true
      - type: volume
        source: elasticsearch
        target: /usr/share/elasticsearch/data
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xmx256m -Xms256m"
      ELASTIC_PASSWORD: changeme
    networks:
      - mynet

  logstash:
    container_name: "logstash"
    build:
      context: logstash/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./logstash/config/logstash.yml
        target: /usr/share/logstash/config/logstash.yml
        read_only: true
      - type: bind
        source: ./logstash/pipeline
        target: /usr/share/logstash/pipeline
        read_only: true
    ports:
      - "5000:5000"
      - "9600:9600"
    environment:
      LS_JAVA_OPTS: "-Xmx256m -Xms256m"
    networks:
      - mynet
    depends_on:
      - elasticsearch

  kibana:
    container_name: "kibana"
    build:
      context: kibana/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./kibana/config/kibana.yml
        target: /usr/share/kibana/config/kibana.yml
        read_only: true
    ports:
      - "5601:5601"
    networks:
      - mynet
    depends_on:
      - elasticsearch

  nginx:
    container_name: "nginx"
    build:
      context: nginx-filebeat/
    volumes:
      - type: bind
        source: /Users/we/Documents/docker/nginx
        target: /etc/nginx
      - type: bind
        source: /Users/we/Documents/docker/nginx_log
        target: /var/log
    ports:
      - "8080:80"
    networks:
      - mynet

networks:
  mynet:
    driver: bridge

volumes:
  elasticsearch:

 

4. 마지막으로 filebeat.xml에서 ssl 통신을 하지 않기 때문에 ssl 부분을 제거해준다. (이유는 하단에 나온다.)

output:
  logstash:
    enabled: true
    hosts:
      - logstash:5044
    timeout: 15

filebeat:
  inputs:
    -
      paths:
        - /var/log/syslog
        - /var/log/auth.log
      document_type: syslog
    -
      paths:
        - "/var/log/nginx/*.log"
      document_type: nginx-access

 

5. 그럼 지금까지 수정한 내용을 이용해서 docker-compose up -d 통해 설치 진행해보자. 설치후 제거 하고 싶으면 (docker-compose -v down) 명령어를 통해 제거 할 수 있다.

docker-compose up -d

 

6. 설치가 완료되면 키바나에 접속해서 확인해보면 logstash, elasticsearch, kibana 모두 설치 된 것을 알 수 있다. 초기 계정은 elastic / changeme이다.

ELK와 nginx docker process

7. logstash pipeline을 만들어줘야하는데 kibana에서 management → logstash → pipeline에서 설정해주면된다. 간단하게 5044포트로 받고 nginx_log 인덱스로 넣게 설정한다.

input {
    beats {
        client_inactivity_timeout => 19909
        port => "5044"
        ssl => false
    }
}
filter {
  if [type] == "nginx-access" {
    grok {
      match => { "message" => "%{NGINXACCESS}" }
    }
  }
}
output {
    elasticsearch {
        hosts => ["elasticsearch:9200"]
        index => "nginx_log"
        user => "elastic"
        password => "changeme"
    }
   stdout { codec => rubydebug }
}

 

8. 그럼 filebeat가 정상적으로 동작하는지 확인해보자.

filebeat 실행 상태 확인

/etc/init.d/filebeat status

 

filebeat.xml 기준으로 설정 정상 적용 상태 확인

filebeat test config

 

filebeat.xml에 설정된 output 정상 여부 확인

filebeat test output

filebeat 테스트 결과

위에 테스트를 진행하면 위에 화면처럼 나오는게 나와야 정상이다. 정상적으로 가동된걸 확인했다.

그럼 실제 nginx에서 나온 로그가 filebeat로 수집되어 logstash > elasticsearch로 정상적으로 적재되는지 보자.
localhost:8080에 접속하여 로그 발생시킨 후 filebeat 로그를 확인했는데 왠걸 다음과 같은 오류가 발생했다.

[2019-10-15T06:12:48,844][INFO ][org.logstash.beats.BeatsHandler] [local: 172.28.0.4:5044, remote: 172.28.0.2:55946] Handling exception: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
[2019-10-15T06:12:48,845][WARN ][io.netty.channel.DefaultChannelPipeline] An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:392) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:359) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:342) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext.access$300(AbstractChannelHandlerContext.java:38) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.channel.AbstractChannelHandlerContext$4.run(AbstractChannelHandlerContext.java:236) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:66) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) [logstash-input-tcp-6.0.3.jar:?]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [logstash-input-tcp-6.0.3.jar:?]
at java.lang.Thread.run(Thread.java:834) [?:?]
Caused by: org.logstash.beats.BeatsParser$InvalidFrameProtocolException: Invalid Frame Type, received: 1
at org.logstash.beats.BeatsParser.decode(BeatsParser.java:92) ~[logstash-input-beats-6.0.0.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) ~[logstash-input-tcp-6.0.3.jar:?]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) ~[logstash-input-tcp-6.0.3.jar:?]
... 10 more

이 문제는 logStash 또는 filebeat가 동시에 ssl을 사용하지 않는데 한쪽만 ssl 통신을 했을 때 발생되는 오류이다.

그래서 아까 위에 filebeat에 ssl 부문을 지우고 pipeline에 ssl 설정을 false로 지정한 것이다. 그럼 다시한번 localhost:8080에 들어가보자.

지금은 index.html을 만들어 놓지 않아서 404 에러가 발생된다.

GET nginx_log/_search
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "nginx_log",
        "_type" : "_doc",
        "_id" : "vc04zm0BZBO8vgBrBmV7",
        "_score" : 1.0,
        "_source" : {
          "ecs" : {
            "version" : "1.1.0"
          },
          "@version" : "1",
          "tags" : [
            "beats_input_codec_plain_applied"
          ],
          "message" : """2019/10/15 07:00:31 [error] 25#25: *16 "/etc/nginx/html/index.html" is not found (2: No such file or directory), client: 172.28.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"""",
          "host" : {
            "name" : "a31d3333d22b"
          },
          "agent" : {
            "type" : "filebeat",
            "version" : "7.4.0",
            "hostname" : "a31d3333d22b",
            "id" : "cc4eb582-e09c-4a83-bb2e-9721c39ee508",
            "ephemeral_id" : "a5918a0b-4688-458f-bbc2-4eb49a3fff03"
          },
          "log" : {
            "file" : {
              "path" : "/var/log/nginx/error.log"
            },
            "offset" : 8524
          },
          "@timestamp" : "2019-10-15T07:00:38.443Z"
        }
      },
      {
        "_index" : "nginx_log",
        "_type" : "_doc",
        "_id" : "vs04zm0BZBO8vgBrBmV8",
        "_score" : 1.0,
        "_source" : {
          "ecs" : {
            "version" : "1.1.0"
          },
          "@version" : "1",
          "tags" : [
            "beats_input_codec_plain_applied"
          ],
          "message" : """172.28.0.1 - - [15/Oct/2019:07:00:31 +0000] "GET / HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"""",
          "host" : {
            "name" : "a31d3333d22b"
          },
          "agent" : {
            "type" : "filebeat",
            "version" : "7.4.0",
            "hostname" : "a31d3333d22b",
            "id" : "cc4eb582-e09c-4a83-bb2e-9721c39ee508",
            "ephemeral_id" : "a5918a0b-4688-458f-bbc2-4eb49a3fff03"
          },
          "log" : {
            "file" : {
              "path" : "/var/log/nginx/access.log"
            },
            "offset" : 8528
          },
          "@timestamp" : "2019-10-15T07:00:38.443Z"
        }
      }
    ]
  }
}

 

정상적으로 적재가 잘되는것을 확인할 수 있다.

이로써 docker로 구성된 elk로 로그 적재를 진행해봤다. 

 

관련 패키지는 github에 올려놓았다.

https://github.com/weduls/elk_with_nginx

맥에서는 docker 설치와 운용이 쉬웠는데, 맥북이 망가지고 윈도우 컴퓨터를 사용하고 있으니 Docker 사용이 생각보다 쉽지 않았다.

그래서 저번에 Windows Subsystem for linux (ubuntu)를 설치하고 여기에 docker를 올려보면 어떨까 싶어서 도전해 보았다. 

우선 docker engine는 WSL에서 실행되지 않아서 호스트 컴퓨터에 Windows용 Docker를 설치해야한다. 그리고 나서 Linux(ubuntu)에서 실행되는 Docker 클라이언트(WSL)가  Windows에 설치된 Docker Engine 데몬으로 명령어를 보내서 운용할 수 있다.

우선 Ubuntu에 Docker를 설치해보자.

1. 우선 패키지를 업데이트 한다.

1
sudo apt-get update
cs


2. 그리고 apt에서 https를 통해 저장소를 사용할 수 있도록 패키지들을 설치한다.

1
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
cs


3. Docker의 공식 GPG키를 추가한다.

1
2
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add 
$ sudo apt-key fingerprint 0EBFCD88
cs


4. Docker를 설치한다.

1
2
$ sudo apt-get update
$ sudo apt-get install docker-ce
cs

이렇게 하면 Linux에 설치가 완료되나 Docker Engine은 WSL에서 실행되지 않으므로 윈도우와 연결해야한다.

먼저 Docker 호스트가 있는 Docker 클라이언트에게 알려주어야 하므로 다음 명령어를 사용한다.

1
2
3
4
5
$ 도커 -H localhost : 2375 이미지
 
// 매번 하기 귀찮으면 환경변수에 등록
$ export DOCKER_HOST = localhost : 2375
$ echo "export DOCKER_HOST = localhost : 2375">> ~ / .bash_profile
cs


그리고 Windows에서 데몬을 공개해주어야 정상적으로 동작한다.


이미지를 다운받아보고 docker 명령어를 사용해보면 정상적으로 연결이 되는 것을 확인할 수 있다.



https://medium.com/@sebagomez/installing-the-docker-client-on-ubuntus-windows-subsystem-for-linux-612b392a44c4

스프링 부트 애플리케이션을 Docker image로 빌드해서 컨테이너에 올리는 작업을 진행해 보겠다.

 

필요사항

  • JDK 1.8 later
  • Maven 3.2 이상
  • STS
  • Docker

 

Pom.xml 수정을 먼저 진행해야한다.

  • wedul 이라는 이름의 jar 파일이 생성된다.
  • docker에서 실행하기 위한 Maven 설정이 들어있는 jar file이 만들어 진다.
  • 만약 image prefix 값을 별도로 지정하지 않으면 artifact id가 명시된다.
<properties>
   <docker.image.prefix>wedul</docker.image.prefix>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>dockerfile-maven-plugin</artifactId>
            <version>1.3.6</version>
            <configuration>
                <repository>${docker.image.prefix}/${project.artifactId}</repository>
                <buildArgs>
                    <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

 

그리고 프로젝트 Root에 Dockerfile을 위치시켜야 한다. 

FROM java:8
VOLUME /tmp
ADD /target/wedulpos-0.0.1-SNAPSHOT.jar wedulpos.jar
ENTRYPOINT ["java","-jar","wedulpos.jar"]

DockerFile 옵션 설명 (출처 : https://www.callicoder.com/spring-boot-docker-example/)

VOLUME 

볼륨은 호스트 OS에서 컨테이너에 의해 생성 된 데이터를 유지하고 호스트 OS에서 컨테이너로 디렉토리를 공유하는 메커니즘입니다.

VOLUME 명령은 컨테이너에 지정된 경로로 마운트 포인트를 작성합니다. 컨테이너를 실행할 때 지정된 마운트 지점이 매핑 될 Hot OS의 디렉토리를 지정할 수 있습니다. 그런 다음, 컨테이너가 마운트 된 경로에 쓰는 것이 호스트 OS의 매핑 된 디렉토리에 기록됩니다. 볼륨의 가장 일반적인 사용 사례 중 하나는 컨테이너에 의해 생성 된 로그 파일을 호스트 OS에 저장하는 것입니다.

예를 들어, 응용 프로그램이 로그 파일을 /var/log/app.log 위치에 기록한다고 가정 해 봅시다. Dockerfile에 / var / log 경로로 VOLUME을 마운트 한 다음 컨테이너를 실행하는 동안이 마운트 지점이 매핑 될 호스트 OS의 디렉토리를 지정할 수 있습니다. 그런 다음 호스트 OS의 매핑 된 디렉토리에서 로그에 액세스 할 수 있습니다.

위의 Dockerfile에서 / tmp 경로를 사용하여 마운트 지점을 만들었습니다. 이것이 스프링 부트 응용 프로그램이 Tomcat에 대한 작업 디렉토리를 기본적으로 만드는 위치이기 때문입니다. 이 스프링 부트 응용 프로그램에는 바람둥이 디렉토리에 관심이 있기 때문에 필수는 아니지만. 그러나 Tomcat 액세스 로그와 같은 항목을 저장하려는 경우 VOLUMES는 매우 유용합니다.

 

ADD

ADD 명령은 새 파일과 디렉토리를 고정 이미지에 복사하는 데 사용됩니다.

 

ENTRYPOINT

응용 프로그램이 컨테이너 내부에서 실행되는 방법을 구성하는 곳입니다. 

 

그렇게 설정을 마치고 명령어를 통해 docker에서 사용할 수 있는 이미지 파일로 빌드를 진행한다.

./mvnw install dockerfile:build

설정을 정상적으로 하고 빌드가 진행이 완료되면 BUILD SUCCESS가 보일 것이다.

Docker image ls 명령어를 통해 지금 설치가 완료된 이미지 파일도 확인할 수 있다.

 

 

Docker의 컨테이너를 실행시키기 위해서는 다음가 같은 명령어를 사용할 수 있다.

 

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
옵션 설명
-d detached mode 흔히 말하는 백그라운드 모드
-p 호스트와 컨테이너의 포트를 연결 (포워딩)
-v 호스트와 컨테이너의 디렉토리를 연결 (마운트)
-e 컨테이너 내에서 사용할 환경변수 설정
–name 컨테이너 이름 설정
–rm 프로세스 종료시 컨테이너 자동 제거
-it -i와 -t를 동시에 사용한 것으로 터미널 입력을 위한 옵션
–link 컨테이너 연결 [컨테이너명:별칭]
/bin/bash bash 쉘 접속 가능


그럼 한번 mysql을 실행 시켜보자.기본적으로 docker run 이미지를 입력하여 컨테이너를 실행시킬 경우에 이미지가 다운받아진 경우가 없는경우에는 Docker 레퍼지토리에서 pull 한후에 컨테이너를 생성하고 시작한다.

-p 옵션의 경우에는 2222:3112로 설정할 수 있다. 이럴경우 호스트에서 2222로 접속하면 내부에서 컨테이너에 6379로 매핑하여 실행한다.

그럼 이 옵션을 이용해서 2개의 mysql을 실행시켜보자.

 

Mysql 이미지 사용하기

기본적인 설명은 Mysql의 Docker 문서를 참고하면 된다.

간편하게 사용테스트를 진행하기 위해서 비밀번호가 없고 보트를 3306과 3310 두개를 사용하는 mysql을 만들고 docker container가 종료되면 바로 삭제하는 옵션을 사용해서 진행해보자. 두 개 모두 서로 다른 방식으로 진행해보자.

먼저 첫번째는 컨테이너를 명령어를 실행시켜서 진행하는 방식을 사용해보자.

docker run -d --rm -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=true --name mysql_test1 mysql:5.7

-e 옵션에 MYSQL_ALLOW_EMPTY_PASSWORD의 경우 패스워드 없이 사용할 수 있는 옵션이다.


명령어를 실행하면 기존에 Mysql의 이미지가 없었기 때문에 이를 다운로드 받는다.

그리고 모든 컨테이너 적제가 완료되면 컨테이너 내부에 mysql을 제어하기 위해 docker 명령어를 사용하여 들어가보자.

docker exec -i -t mysql_test1 bash

 

 

성공적으로 접속한 것을 확인할 수 있다. 접속해서 User를 만들고 접속권한을 부여한다.

그 다음 쿼리박스를 이용해서 접속을 시도해보자. 성공적으로 접속을 완료했다.

 

그럼 이제 3307 포트를 사용하는 다른 Mysql을 docker에서 제공하는 docker-compose를 사용하여 만들어보자.

docker-compose란 무엇인가?

우선 기존에는 docker에 필요한 이미지를 받고 실행 방법을 설정하기 위해서 일일히 명령어에 추가하기에는 어려움이 있다. 방금 전 까지만 해도 비밀번호 없게 하라, 포트는 무엇으로 하라 등등 많은 설정을 명령어 라인으로 실행했었다. 

하지만 이를 yml 문법에 맞게 설정파일을 작성한 뒤에 docker 레포지토리에서 다운을 요청하면 그에 맞게 실행파일 및 설정이 적용되어 컨테이너에 이미지를 적재 시켜준다. 훨씬 간편하다.

그럼 우선 yml 문법으로 mysql 이미지를 실행하는 문법을 만들어보자.

version: '3.1'
services:
  db:
    image: mysql:5.5
    container_name: mysql5.5_test2
    ports:
      - "3307:3306"
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=dbsafer00
      - MYSQL_USER=wedul
      - MYSQL_PASSWORD=dbsafer00
      - MYSQL_DATABASE=wedul

문법은 생각보다 간단하다. image 버전과 container_name을 적고 포트의 인바운드 아웃바운드를 적어준다. 그리고 추가적인 환경으로 mysql의 정보를 기재할 수 있다. 

그리고 docker-compse 명령어로 컨테이너에 등재시킬 수 있다.

docker-compose up -d

도커 프로세스를 확인해보면 두 개의 mysql 컨테이너가 실행중인 것을 확인할 수 있다.

 

두 번째에 실행시킨 Mysql도 동일하게 QueryBox에서 사용이 가능하다.

 

기존에는 DBMS가 필요하거나 할 때, VM을 사용하거나 실 서버를 사용하였지만 도커를 이용하면 아주 간단하게 사용할 수 있어서 좋은 것 같다. 이번에는 Mysql만 해보았지만, Repository에 있는 워드프레스나 레디스 등등도 사용할 수 있따. 실무에서도 시간이 나면 환경을 만들어서 써봐야겠다. 

 

 

Mac OS에 Docker 설치는 매우 간단하다.

하단 링크로 들어가서 Docker.dmg파일을 다운받고 설치를 우선 진행한다. 

Docker 설치링크

 

DMG파일을 실행하면 다음과 같은 창이 출력되고 드래그 하여 Application으로 옮겨주면 된다.

 그 다음 도커를 실행하면 작업표시줄에 출력되고 클릭하면 자세한 상태와 기본 내용이 출력된다.

이제 시작해보자.  도커 시작!

 

 

+ Recent posts