0%

Docker를 이용한 Jenkins 컨테이너 만들기(docker in docker)

Created: Oct 14, 2019 8:41 PM

들어가기

이번에는 ubuntu 18.04 가상머신에 docker, docker-compose 를 사용해서 jenkins 컨테이너를 생성해보겠다.

도커 관리를 위해서 Dockerfile, docker-compose 모두 사용할 예정이다.

이번에 만들 jenkins 컨테이너는 내부에서 dockerfile을 빌드할 예정이므로, docker in docker로 jenkins 이미지를 만들 예정이다.

1. Docker관리를 위한 폴더와 파일 구조

1
2
3
4
5
6
docker (도커 관리 폴더)
|-- jenkins_with_docker (도커를 포함한 젠킨슨 관리 폴더)
|-- jenkins_home (젠킨스 컨테이너의 볼륨 연결용 폴더, 빈 폴더 준비)
|-- docker-compose.yml (실제 컨테이너를 생성하는 docker-compose 파일)
|-- Dockerfile (docker-compose.yml에서 빌드할 jenkins 파일)
|-- docker_install.sh (Dockerfile에서 호출할 docker 설치 스크립트 파일)

구조는 아래와 같다.

이미 스스로가 docker 컨테이너는 jenkins에서 docker를 사용하기 위해서는 컨테이너가 동작하는 호스트의 docker를 빌려서 사용하는 것이다.

연결 방법은 docker volume을 사용해서 docker 가 설치된 host의 /var/run/docker.sock과 jenkins 컨테이너 내부의 /var/run/docker.sock를 연결해 줘야 한다.

이 부분은 docker-compose.yml 에 설정 할 것이다.

2. 설정 파일들 준비

Dockerfile

1
2
3
4
5
6
7
8
9
FROM jenkins/jenkins:lts

USER root

COPY docker_install.sh /docker_install.sh

RUN chmod +x /docker_install.sh

RUN /docker_install.sh

jenkins이미지에 docker_install.sh 파일을 복사해서 가져와서 실행한다.

Dockerfile 내부에서 RUN 명령등으로 쉘 스크립트를 실행할수 있지만, 뭔가 계속 오류가 나서 이런 방식을 선택했다.

docker_install.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh

apt-get update && \
apt-get -y install apt-transport-https \
ca-certificates \
curl \
gnupg2 \
zip \
unzip \
software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce

위 내용은 jenkins Dockerfile을 빌드할때 동작하는 스크립트이다.

내용은 jenkins 내부에 docker를 설치하는 것이다.

개인적으로 이렇게 sh파일을 copy하는게 편해보인다.

구글링으로 얻을수 있는 쉘스크립트를 바로 쓸수 있기 때문이다.

만약 jdk, maven, gradle, node등 필요한게 있다면 이런식으로 추가해 주면 될것 같다.

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: '3'

services:
jenkins:
build:
context: .
dockerfile: Dockerfile
container_name: 'jenkins_docker'
restart: always
ports:
- '8200:8080'
- '50200:50000'
expose:
- '8080'
- '50000'
volumes:
- './jenkins_home:/var/jenkins_home'
- '/var/run/docker.sock:/var/run/docker.sock'
environment:
TZ: "Asia/Seoul"
networks:
default:
external:
name: devops

docker-compose.yml에서는 이미지 대신 Dockerfile을 이용해서 이미지를 대신한다.

volumes에서 docker.sock을 host와 연결한다.

3. 컨테이너 생성

1
sudo docker network create devops

혹시나 나중에 쓸지도 모를 네트워크를 일단 생성하자.

그리고 docker-copomse.yml 이 있는 경로로 이동해서 아래 명령으로 이미지 생성과 jenkins 컨테이너를 생성하자.

1
sudo docker-compose up -d

위 명령얼 실행하면 Dockerfile을 빌드하기 시작한다. 이때 jenkins 이미지를 받고 docker를 설치하므로 시간이 좀 걸린다.

그리고 완료가 되면 아래처럼 나올것이다.

그리고 docker 이미지를 검색해보면 아래처럼 상당이 큰 이미지가 생성된다.

만약 위 과정에서 문제가 생긴다면, Dockerfile을 별도로 빌드해서 Dockerfile에서 오류가 나는지, docker_install.sh에서 오류가 나는지, 아니면 docker-compose.yml 에서 오류가나는지 찬찬히 찾아봐야 한다.

4. jenkins 컨테이너에 접속해서 docker 명령어 테스트

브라우저에서 http://localhost:8200 에 접속해보자.

일반적인 jenkins 초기화면이 나온다 초기 비밀번호를 찾아서 넣어주자.

공식 jenkins 이미지를 사용하므로 플러그인도 잘 설치가 된다.

관리자 정보를 입력하고 save and Continue 를 눌러주자.

아까 생성했던 관리자 계정으로 로그인하자.

위처럼 정상적으로 젠킨스가 동작한다.

만약 화면이 하얀색으로 멈춰 있거나 먹통이 되면 docker-compose stop → docker-compose start 로 컨테이너를 재시작 해보길 바란다.

이제 간단한 job을 만들어서 docker 명령어가 jenkins에서 호출되는지 확인해보자.

빨간 줄만 따라가면된다. Execute shell에 docker ps 입력하고 저장을 눌러 job을 저장하자.

이렇제 잡이 생성되면 들어가서

Build now눌러서 결과를 확인하자.

짜잔 jenkins에서 docker ps 명령어가 동작한 것을 확인 할 수 있다.

앞서 말했듯이 jenkins 컨테이너에서 호스트 docker를 빌려 쓰므로 현재 호스트의 docker ps 가 동작하고 그 결과가 나온다.

끝!

참고자료중

https://getintodevops.com/blog/the-simple-way-to-run-docker-in-docker-for-ci

를 가장 많이 참고했다.

참고자료

https://getintodevops.com/blog/the-simple-way-to-run-docker-in-docker-for-ci

https://medium.com/@NovaWoo/docker-와-jenkins-를-사용한-안드로이드-ci-1-9510178a525f

https://www.44bits.io/ko/post/almost-perfect-development-environment-with-docker-and-docker-compose#docker-compose.yml-파일

https://github.com/jenkinsci/docker

들어가기

가상머신 ubuntu 18.04 에 docker compose를 이용해서 jenkins를 설치해보도록 하겠다.

기본적으로 ubuntu에 docker와 docker-compose 가 설치되어 있어야 한다.

1.docker 관리용 폴더구조 만들기

1
2
3
4
docker
|-- jenkins
|-- jenkins-home
|-- docker-compose.yml

jenkins-home은 jenkins 컨테이어의 볼륨을 연결할 호스트 디렉토리이다.

컨테이너가 동작하면서 jenkins-home에 파일을 작성할 텐데 혹시나 발생할 문제에 대비해 jenkins-home 권한을 풀어주자.

1
chmod -R 777 jenkins-home

2.docker-compose.yml 파일 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: '2'

services:
jenkins:
image: 'jenkins/jenkins:latest'
container_name: 'jenkins'
restart: always
ports:
- '8100:8080'
- '50100:50000'
expose:
- '8080'
- '50000'
volumes:
- './jenkins_home:/var/jenkins_home'
environment:
TZ: "Asia/Seoul"
networks:
default:
external:
name: devops

혹시나 사용할까 해서 docker network를 생성하자.

아래 명령으로 명령으로 docker network를 생성할 수 있다.

1
sudo docker network create devops

아래처럼 생성한 docker network 를 확인 할 수 있다.

3. jenkins docker 컨테이너 생성

docker-compose.yml이 있는 경로에서 아래 명령으로 컨테이너를 생성하자.

1
sudo docker-compose up -d

위처럼 젠킨스 실행을 확인했다.

브라우저에서 http://localhost:8100 으로 접근해보자.

젠킨스 동작을 확인 할 수 있다.

docker/jenkins-home/secrets/initialAdminPassword 파일을 열어보면 초기화 비밀번호를 확인 할수 있다.

해당 파일을 찾아 비밀번호를 입력해주자.

이 다음부터는 일반적인 젠킨스와 동일하다.

끝!!!

ubuntu에 docker compose로 gitlab 설치하기

들어가기

가상머신의 ubuntu18.04에 docker compose를 통해서 gitlab 서버를 설치해 보겠다.

ubuntu에 미리 docker, docker-compose 가 설치되어 있어야 한다.

1.docker 관리 폴더 만들기

1
2
3
4
5
6
7
8
docker
|-- gitlab
|-- gitlab-data
| | -- backups
| | -- config
| | -- data
| | -- logs
|-- docker-compose.yml

docker-compose.yml 파일과 컨테이너와 연결할 호스트 볼륨을 저장할 gitlab-data 폴더를 생성했다.

2. docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
web:
image: 'gitlab/gitlab-ce:latest'
restart: always
container_name: 'gitlab'
hostname: '127.0.0.1'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://127.0.0.1:8929'
gitlab_rails['gitlab_shell_ssh_port'] = 2224
ports:
- '8929:8929'
- '2224:22'
volumes:
- './gitlab-data/config:/etc/gitlab'
- './gitlab-data/logs:/var/log/gitlab'
- './gitlab-data/data:/var/opt/gitlab'
- './gitlab-data/backups:/var/opt/gitlab/backups'

gitlab 공식 사이트에 가면 gitlab을 위한 docker-compose 파일을 제공한다.

그걸 받아서 볼륨과 포트정도만 수정했다.

위 127.0.0.1로 설정된 부분은 필요한데로 고정아이피를 넣어주면 된다.

지금은 실습을 위해 그냥 로컬호스트아이피로 박았다.

참고로 external_url에 설정된 ULR은 나중에 gitlab서버에서 생성된 리파지토리의 clone할 URL로 제공된다.

3.혹시나 해서 gitlab-data 권한부여

이건 내가 공부가 적어 잘 모르겠다.

혹시 컨테이너 실행시 볼륨에 파일을 쓰면서 권한 오류가 발생할까 싶어, gitlab-data 폴더에 777권한을 부여 할 것이다.

1
chmod -R 777 gitlab-data

-R 옵션은 gitlab-data폴더와 하위 폴더 모두에 한꺼번에 권한을 주는 옵션이다.

4. gitlab 도커 컨테이너 올리기

1
sudo docker-compose up -d

위 명령어로 gitlab 도커 컨테이너를 올리자.

위처럼 빨리 done이라 나오지만 gitlab은 컨테이너가 부트가 되는 시간이 좀 길다.

로그를 확인해보자.

1
sudo docker-compose logs -f -t --tail="all"

한참 로그가 나오다가 위처럼 http에 관한 로그가 나오기 시작하면 접속이 가능한 상태가 된다.

1
sudo docker-compose ps

위 명령어로 컨테이너 상태를 확인해보자.

status가 up으로 되어있으면 정상인 것이다.

문제가 있는경우 계속 starting 인 경우가 있으니, 그땐 로그를 보고 해결책을 찾아야 한다.

5. gitlab에 접속해서 컨테이너 동작 확인하기.

브라우저에 http://127.0.0.1:8928 로 접속해보자.

root 계정의 비밀번호를 입력해주자.

그 다음 root/방금생성한 비멀번호 를 입력해서 로그인하자.

그러면 Administrator 로 로그인 하게 된다.

아래는 테스트 프로젝트를 push 해서 동작을 확인한 화면이다.

ubuntu 1804 dockercompose nexus 설치

들어가기

ubuntu 18.04 버전에 docker compose를 사용해서 nexus를 도커로 설치하고 실행해 보려 한다.

미리 ubuntu에 docker와 docker compose 가 미리 설치되어 있어야 한다. 해당 내용은 이 글에 포함되어 있지 않다.

실습

1. 도커 관리용 폴더 만들기

생성할 도커 컨테이너의 설정파일과 볼륨을 관리하기 위해 폴더를 만들 것이다.

나느 아래처럼 홈디렉토리에 생성할 것이다.

1
mkdir -p ~/docker/nexus/nexus-data

그리고 nexus폴더 내부에 docker-compose.yml 파일을 생성하고, 그 내용을 아래처럼 만들어 준다.

2. docker-compose.yml 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '2'

services:
nexus3:
image: 'sonatype/nexus3:latest'
container_name: 'nexus3'
restart: always
expose:
- '8081'
ports:
- '8081:8081'
volumes:
- './nexus-data:/nexus-data'
environment:
TZ: "Asia/Seoul"
networks:
default:
external:
name: devops

포트를 8081로 셋팅하였고, nexus-data를 /home/docker/nexus-data 에 볼륨으로 연결 하였다.

네트워크를 쓸지는 모르겠지만 devops로 설정했다.

다음 명령어로 devops라는 docker network를 생성하자.

1
sudo docker network create devops

3. docker-compose.yml로 nexus 컨테이너 생성

아래 명령어를 docker-compose.yml이 있는 경로에서 실행하자.

1
sudo docker-compose up -d

위 명령얼 실행하면 자동으로 nexus 이미지를 다운받고 nexus 컨테이너를 실행 시킬 것이다.

아래 명령으로 docker compose로 실행한 컨테이너를 확인하자.

물론 docker-compose.yml 이 있는 경로에서 명령어를 입력해야 한다.

1
sudo docker-compose ps

state에 up으로 컨티에너가 실행 되었다는 것을 알 수 있다.

docker-compose.yml 파일이 있는 경로에서 아래 명령어를 치면 해당 컨테이너의 log를 볼수도 있다. 만약 컨테이너에 문제가 있다고 생각하면 로그를 확인 하자.

1
sudo docker-compose logs -f -t --tail="all"

음 브라우저에 아래 주소로

1
http://localhost:8081

접근했는데 페이지가 열리지 않아서 로그를 확인해보니 위와 같은 오류가 로그로 나오고 있다.

아마 nexus컨테이너가 동작하면서 호스트의 nexus-data 에 파일을 생성해야 하는데 권한 문제가 있나보다.

일단 실행중인 컨테이너를 멈추고 해당 폴더에 권한을 부여해봐야 겠다.

아래 명령어로 실행시킨 nexus 컨테이너를 정지 시키자.

물론 docker-compose.yml 이 있는 경로에서 실행하자.

1
sudo docker-compose down

위 명령어로 해당 컨테이너를 종료하고 이미지를 삭제한다.

그리고 아까 생성했던 /home/docker/nexus/nexus-data 폴더의 권한을 수정해보자.

1
sudo chmod 777 /home/docker/nexus/nexus-data

무언가 도커에 다른 방법이 있을거 같은데, 나는 공부가 적어 위처럼 모든 권한을 다 부여했다.

다시 도커 컨테이너를 올려보자.

1
sudo docker-compose up -d

그리고 로그를 확인해보자.

1
sudo docker-compose logs -f -t --tail="all"

뭔가 에러 없이 넥서스가 실행 되었다고 나온다.

이제 브라우저에서 http://localhost:8081로 접근해보자.

위 처럼 동작을 확인 할 수 있다.

우측 오른쪽의 sign in 을 클릭하면 다음처럼 나온다.

/nexus-data/admin.password 파일을 확인해서 admin의 비밀번호를 확인해 보라고 한다.

예전 버전의 nexus는 기본으로 admin/1234 인데 요즘 버전은 이렇게 나온다.

초기에 생성한 볼륨 폴더인 /home/docker/nexus/nexus-data 폴더로 이동해보자.

admin.password 파일이 있다. 해당 파일을 열어보면 admin 초기 비밀번호가 나온다.

admin/초기비밀번호를 입력하면 새로 생성한 넥서스에 관리자로 로그인 할 수 있다.

로그인에 성공하면 다음처럼 나온다.

새 비밀번호를 입력한다.

그리고 대충 next 버튼을 눌러주면 된다.

4. 이제 관리는 어떻게?

도커 컨테이너의 볼륨을 호스트와 연결했으니, 컨테이너를 지워도 호스트 볼륨이 살아 있으니, 문제 없다.

나중에 백업을 하거나 컨테이너를 다른곳으로 옮겨야 할 경우 /home/docker 폴더를 전체 복사해서 이동시켜 컨테이너를 올리면 작업중엔 넥서스의 상태를 보존할수 있을거 같다.

5. docker compose 명령어 요약

1
2
3
4
5
6
7
8
docker-compose up -d      // 도커컴포즈 컨테이너 생성 및 실행   
docker-compose down // 도커컴포즈 컨테이너 종료 및 삭제

docker-compose start // 도커컴포즈 컨테이너 실행
docker-compose stop // 도커컴포즈 컨테이너 종료

docker-compose ps // 도커컴포즈 프로스스 확인
docker-compsos logs -f -t --tail="all" //도커컴포즈 로그 확인

ubuntu 18.04 docker compose install

들어가기

이전에 ubuntu 18.04 가상머신에 docker 를 설치했다.

윈도우에는 docker for windows로 설치하면 docker와 docker compose 가 한방에 설치되는데, ubuntu에서는 docker compose를 추가로 따로 설치해 줘야 한다.

이번에는 docker compose를 설치 해보려 한다.

ubuntu 18.04 docker compse 설치하기

https://github.com/docker/compose/releases

일단 위 경로로 가서 최신 docker compose 버전을 살펴보자 rc 버전은(release candidate)로 일종의 베타 버전이라고 한다.

원하는 버전을 선택해서 설치하면 된다.

sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

간단하게 설치를 할 수 있다.

참고자료

https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04

springboot gradle 멀티모듈 프로젝트 만들기

개발환경

windows10

intelliJ 커뮤니티 버전

jdk1.8

들어가기

intelliJ 에서 gradle 기반 springboot 멀티 모듈 프로젝트를 한번 만들어보자.

멀티모듈을 어떻게 디자인 해야하는지는 다루지 않고, gradle을 이용해서 어떻게 모듈을 생성하고 모듈간의 의존성을 셋팅을 하는가에 중점을 둔 글이다.

1. 인텔리J에서 gradle 프로젝트 생성

일단 intelliJ 에서 gradle 프로젝트를 생성하자.

http://start.spring.io 로 이동해서 spring boot gradle 프로젝트를 생성하자.

(나는 인텔리J 커뮤니티 버전을 사용중이라 springboot 프로젝트를 생성할 때 주로 http://start.spring.io 를 주로 사용한다.)

아래처럼 설정하여 프로젝트를 생성하자.

Project : Gradle Project

Language: Java

Spring Boot: 2.1.9(현재 2점대 이상으로 선택하면 될듯)

Project Metadata

Group : com.hanumoka.go

Artifact : go-modules

Option

Packaging: War

Java : 8

Dependencies: Spring web, Thymeleaf

위에서 중요한 부분은 Group이다.

com.hanumoka.go는 이 프로젝트의 공통 패키지이며, Artifact: go-modules 는 실제 생성되는 실행 프로그램을 구별하는 명칭이 아니라 모듈의 묶음일 뿐이다.

개인적으로 프로젝트를 war로 설정해서 생성한 이유는 애시당초 war로 프로젝트를 생성하면 프로젝트 빌드시 war, jar 빌드가 쉽기 때문이다.

Spring web은 필수이며 Thymeleaf 스프링 컨트롤러의 동작을 확인하기 위해 추가했다.

설정된 값을을 이용해서 압축된 프로젝트를 다운로드 하자.

해당 파일을 압축을 풀고 intelliJ로 open하자.

위처럼 gradle 이 제대로 빌드 되는지 확인하자.

컨트롤러와 html 페이지를 추가해서 스프링 컨트롤러 동작을 한번 시켜보자.

com.hanumoka.go.gomodules 패키지를 선택하고 controller 패키지를 생성하고, HomeController를 생성하고 html을 리다이렉팅 할 수 있는 RequestMapping 메소드를 추가하자.

GoModulesApplication 를 선택하고 run 시켜서, http://localhost:8080 로 접속해서 컨트롤러가 동작하는지 보자.

스프링 컨트롤러가 정상 동작하는 것을 확인 할 수 있다.

2. 모듈 추가하기

1번에서 한 일은 그냥 gradle 기반 springboot 프로젝트를 생성하고 동작시킨 것이다.

이제 실제 사용할 모듈을 구상하고 추가하고 셋팅해야 한다.

이 글은 gradle환경에서 springboot 프로젝트에서 모듈을 추가하고 연결하는 설정을 기록하는데 초점을 두고 있다.

따라서 간단하게 Service 모듈과 Controller 모듈을 생성하고 Controller 모듈이 Service 모듈을 사용하는 시나리오로 진행 하겠다.

실제 모듈을 설계하고 구성하는 일은 많은 고심이 필요해 보인다.(이부분은 나중에 좀 연구해 봐야 겠다.)

일단 프로젝트 루트 경로의 build.gradle, settigns.gradle 은 프로젝트 전체에 적용되는 설정 파일이다.

사실 gradle을 잘 몰라 자세한 설명을 할 수가 없다.

하지만 이후로 추가될 모듈에 관련된 공통적인 설정은 이 두 파일에 기술된다.

그리고 프로젝트 루트 경로의 src 폴더는 모듈을 추가함으로써 사라질 예정이다.

일단 a-module 이라는 이름의 모듈을 추가해보자.

일단 루트 경로의 settings.gradle을 확인해보자.

아래처럼 루트 프로젝트 네임만 있을 것이다. 나중에 모듈을 추가하면 모듈 정보가 추가될 것이다.

일단 넘어가자.

rootProject.name = 'go-modules'

그리고 루트 경로의 build.gradle 파일을 열어서 내용물을 모두 지우고 아래 내용으로 치환하자.

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
buildscript {
ext {
springBootVersion = '2.1.9.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath "io.spring.gradle:dependency-management-plugin:1.0.8.RELEASE"
}
}

subprojects {

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.hanumoka.go'
version = '0.0.0'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}


task initSourceFolders {
sourceSets*.java.srcDirs*.each {
if( !it.exists() ) {
it.mkdirs()
}
}

sourceSets*.resources.srcDirs*.each {
if( !it.exists() ) {
it.mkdirs()
}
}
}

dependencies {
compileOnly('org.projectlombok:lombok')
}
}

buildscript 부분에서 공통 스브링부트 버전등을 설정한다.

subprojects 는 추가될 모듈둘에 공통적으로 적용될 내용들이 기술되어 있다.

이제 a-module 을 추가해보자. 아래처럼 모듈을 클릭하자.

아래 내용을 확인하고 next 버튼을 클릭하자.

아래처럼 GroupId : com.hanumoka.go

ArtifactId : a-module 를 입력하고 next 를 클릭하자.

a-module 이라는 모듈명을 확인하고 Finish 버튼을 클릭하자.

아래처럼 a-module이 추가되었다. 하늘색 작은 상자가 표시 되어 있는데, 이것이 모듈을 의미한다.

모듈을 추가했으니 프로젝트 루트 경로의 settings.gradle 파일을 열어보자.

아래처럼 include ‘a-module’ 이라는 내용이 자동으로 추가된 것을 확인 할 수 있다.

만약 없다면, 추가해주자.

그리고 루트 경로의 settings.gradle 파일을 열어보자.

그리고 아래 내용을 추가해주자. 프로젝트해당 모듈이 있다고 등록하는 것이다. 나중에 이 모듈에 대한 디펜던시도 추가할 것이다.

project(':a-module') {
}

이제 생성했던 a-module 폴더로 들어가서 a-module 폴더의 build.gradle 파일을 열어보자.

기존의 내용들을 지우고 아래 내용으로 치환하자.

이 파일은 a-module을 위한 독자적인 파일이다.

dependencies {
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

그리고 아래처럼 모듈 내부의 패키지와 파일들을 구성하자.

공통 패키지는 com.hanumoka.go 이다.

BeanScan의 기준이 되는 AmoduleApp.class, ServletInitializer.class 는 이곳이 둬야 한다.

아래 AmoduleApp.class 파일이다.

package com.hanumoka.go;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AmoduleApp {
    public static void main(String[] args) {
        SpringApplication.run(AmoduleApp.class, args);
    }
}

아래 ServletInitializer.class 파일이다.

package com.hanumoka.go;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(AmoduleApp.class);
    }

}

아래 HomeController.class 이다.

추가한 a-module을 다른 모듈과 구분하기 위해 amodule 을 추가했다.

package com.hanumoka.go.amodule.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/")
    public String index(){
        return "index";
    }
}

기존에 루트 경로에 있던 src 폴더 내부를 옮겨와서 AmoduleApp 파일명만 변경한 것이다.

그리고 루트 경로의 src 폴더를 제거하자.

지금까지 작업은 기존의 단일 프로젝트의 src를 a-module로 옮긴것이다.

AmoduleApp 을 실행 하면, 스프링 앱이 동작하고 http://localhost:8080 접근시 index.html 이 보여야 한다.

지금까지 한 작업으로 springboot 단일 프로젝트를 springboot 모듈 프로젝트로 변경 되었다.

이제 b-module을 추가하고 a-module에서 b-module을 사용하는 구조로 만들어보자.

b-module 이라는 모듈 추가하기

앞서 a-module 추가하듯이 b-module도 추가하자.

아래와 같은 구조가 될 것이고, src 폴더는 앞에서 말한것 처럼 제거 했다.

루트 경로에 settings.gradle 폴더르르 보면 역시 b-module 이 추가된 것을 볼 수 있다.

루트 경로의 build.gradle 에 가서 앞서 등록했던 a-module에 dependencies 를 방금 추가한 b-module 로 추가하자.

해당 내용은 a-modul이 b-module에 의존성을 갖는다는 것이다.

project(':a-module') {
    dependencies {
        compile project(':b-module')
    }
}

b-module을 열고 아래처럼 구성하자.

b-module은 a-module에서 사용할 서비스로 Bservice.class를 제공한다.

Bservice.class

package com.hanumoka.go.bmodule.service;

import org.springframework.stereotype.Service;

@Service
public class Bservice {
    public String test(){
        return "Bservice test()...";
    }
}

그리고 b-module 하위이 build.gradle 을 아래로 치환 했다.

bootJar{
    enabled = false;
}
jar {
    enabled = true;
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter')
}

A-module 컨트롤러에서 B-module 서비스를 사용해보자.

A-module의 HomeController.class를 아래처럼 수정하자.

package com.hanumoka.go.amodule.controller;

import com.hanumoka.go.bmodule.service.Bservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @Autowired
    Bservice bservice;

    @RequestMapping("/")
    public String index(){

        System.out.println(bservice.test());
        return "index";
    }
}

이제 a-module의 AmoduleApp을 실행해서 a-module의 컨트롤러가 동작할때 b-module의 서비스를 호출하는지 확인해보자.

아래처럼 동작을 확인 할 수 있다.

go-module 을 빌드하면 아래처럼 a-module 의 빌드 결과물을 확인 할 수 있다.

화면에는 안 보이지만 b-module 역시 빌드 결과물이 생겼다.

하지만 a-module에만 SpringBootApplication을 등록했으므로, 실제 실행 시킬수 있는 jar 파일은

a-module-0.0.0.jar 이다.

직접 jar를 실행시켜, 정상동작을 확인하자.

java -jar a-module-0.0.0.jar 

마무리

이 글에서는 간단하게 gradle 기반으로 spring boot 멀티 모듈을 구축하는 방법을 정리하였다.

사실 실제 멀티 모듈을 설계하고 구축하는 일은 어려운 일이고, 많은 고민이 필요해 보인다.

하지만 나처럼 gradle 자체를 잘 모르는 사람은 실제 구축 자체가 어려운 일이므로 이렇게 기록해 놓는다.

해당 예제는 아래 github에 올려놓았다.

https://github.com/hanumoka/springboot_gradle_multimodule

참고자료

https://bkjeon1614.tistory.com/38

들어가기

ubuntu에서 이것저것 하다보면 관리자 권한으로 명령어를 실행해야 할 일이 많다.

매번 sudo 명령어 형태로 명령어를 호출하기에는 불편하다.

sudo su로 root 계정으로 로그인 하여 명령어를 호출하면 간단하지만, 이때 문제가

cd ~ 명령어 사용히 /root 로 이동한다는 것이다.

이런 경우 일반계정을 아예 root 계정의 그룹에 추가하면 해결이 된다.

일반계정에 root 계정 권한 부여

나는 현재 가상머신 ubuntu에 hanumoka-ubuntu1804라는 일반계정을 가지고 있다. (아 계정명을 너무 길게 지었다.)

일단, 환경설정 파일을 수정하기 위해 관리자로 로그인하자.

1
sudo su

그리고 /etc/sudoers 파일을 수정해야 한다.

1
2
3
4
vim /etc/sudoers

root All=(ALL:ALL) ALL
일반계정명 All=(ALL:ALL) ALL <- 이렇게 추가하자.

파일을 저장하자.

그리고 /etc/passwd 파일을 수정하자.

1
vim /etc/passwd

맨 위에 보면 root:x 뒤에 0:0 가 보일것이다.

앞의 0은 uid(유저아이디) 뒤의 0는 gid(그룹아이디)를 의미하는데, 슈퍼유저의 uid는 0 슈퍼유저의 gid도 0이다.

root의 권한이 필요한 일반계정의 uid와 gid를 0:0으로 변경해주자.

내 경우 hanumoka-ubuntu1804라는 계정의 uid:gid를 0:0으로 변경했다.

해당 파일을 저장하자.

그리고 root 그룹에 일반계정 hanumoka-ubuntu1804를 포함시키자.

vim /etc/group

파일을 열면 맨위 root:x:0: 이렇게 되어 있을 것이다.

root:x:0: 를 root:x:0:일반계정명 으로 수정해주자.

파일을 저장하고 나온 뒤, root 권한을 부여한 계정에 접속해서 sudo 없이 명령어가 실행되는지 확인해보자.

기존에는 sudo docker ps 로 실행 해야 했지만, 이제는 docker ps 로 바로 명령어가 실행되는 것을 확인 할 수 있다.

끝.

참고로 일반계정아 root 권한을 주면 우분투 로그인시 목록에 나오지 않는다.

참고자료

https://itgameworld.tistory.com/75

https://crasy.tistory.com/149

https://zetawiki.com/wiki/사용자아이디_UID,_그룹아이디_GID

개발환경

springboot

jdk1.8

mysql

mybatis

들어가기

spring 개발을 할때, 난 일반적으로 mybatis를 사용했다.

음 빨리 JPA도 공부를 해야 할거 같다.

일반적으로 mybatis를 사용할 경우 ${param1}, #{param2} 이렇게 두가지 방식으로 파라미터를 받을 수 있다.

나는 보통 #{param2} 를 사용해서, 파라미터를 받는다.

1
2
3
4
SELECT *
FROM USERS
WHERE 1=1
AND USER_ID = #{userId}

#{} 는 내부적으로 PreparedStatement를 사용하기 때문에 SQLInjection 공격에 안전하기 때문이다.

참고로 PreparedStatement는 값을 바인딩 하는 시점에서 전달된 값에 대한 특수문자, 쿼리등을 필터링하여 SQLinjection을 막는다.

이렇게 mybatis를 사용할때 보통 #{}를 사용하면 평화롭고, 안전한 코딩을 할 수 있다.

하지만 간혹 ${}를 사용하고 싶을 때가 있다.

바로 쿼리의 파라미터로 컬럼명을 화면에서 받고 싶은 경우인데,

보통 select 쿼리에 호출시 정렬을 하고 싶을 때 이다.

1
2
3
4
5
6
7
SELECT
USER_ID
,USER_NAME
,USER_AGE
FROM USERS
WHERE 1=1
ORDER BY ${sort_column} ${sort_type}

위처럼 동적으로 파라미터를 외부에 주입 받기 위해서는 #{}가 아닌 ${}를 사용 해야 한다.

${}는 입력받은 파라미터를 쿼리에 직접 치환해주기 때문이다.

하지만 여기에는 SQLInjection에 대한 위험이 존재한다.

어떻게 하면 ${}를 사용하면서, SQL Injection을 방어 할 수 있을까?

스프링 필터를 사용해야하나? AOP를 사용해야 하나?

고민하다가 인터넷에서 괜찮은 라이브리러를 찾았다.

어노테이션으로 SQL Injection 방어

https://github.com/rkpunjal/sql-injection-safe/

sql-injection-safe 라는 라이브러리이다.

아이디어는 어노테이션으로 VO의 필드에 SqlInjection을 방어하는 방법이다.

아래로 가보면 간단하게 테스트 할수 있는 예제 샘플도 있다.

https://github.com/rkpunjal/sql-safe-annotation-example

일단 동적으로 sql의 컬럼을 받을 VO를 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
class GridDto{
private String sortColumn;
private String sortType;

//getter, setter 생략
}

class SomethingGridDto extends GridDto {
private int column_1;
private int column_2;
private int column_3;
//getter, setter 생략
}

위처럼 대충 DTO를 만들었다.

자 이제 라이브러리를 추가하자. pom.xml 아래처럼 추가하자.

1
2
3
4
5
<dependency>
<groupId>com.github.rkpunjal.sqlsafe</groupId>
<artifactId>sql-injection-safe</artifactId>
<version>1.0.2</version>
</dependency>

라이브러리가 추가 되었다면, Sql Injection 위험이 있는 DTO의 필드에 @SQLInjectionSafe 어노테이션을 추가해준다.

1
2
3
4
5
6
class GridDto{
private @SQLInjectionSafe String sortColumn;
private @SQLInjectionSafe String sortType;

//getter, setter 생략
}

그리고 해당 DTO를 받은 컨트롤러에서 @valid를 적용해주면 된다.

굉장히 간단하게 스프링 컨트롤러에서 전달되는 파라미터에 어노테이션으로 SQL Injection을 방어 할 수 있다.

1
2
3
4
@RequestMapping(value = "/queryGrid", produces=APPLICATION_JSON_UTF_8)
public @ResponseBody WebResponse queryGrid(@Valid @ModelAttribute() SomethingGridDto paramDto) {
// ...
}

@Valid 가 동작하면서 @SQLInjectionSafe 동작한다.

만약 sortColumn 멤버변수에 SQL Injection의 위험이 되는 문구가 있는 경우 BindException 이 발생하게된다.

1
2
3
4
5
6
@ExceptionHandler(BindException.class)
public @ResponseBody WebResponse handleBindException(BindException be ){
return new MyResponseObject(false,
getBindExceptionMessage(be) // custom method to find and send an appropriate response
);
}

https://github.com/rkpunjal/sql-safe-annotation-example

위 예제 샘플을 받아서 구동해보면 아래처럼 @SQLInjectionSafe 내용을 확인 할 수 있다.

SQLInjection을 정규식으로 잡아내는 것을 확인 할 수 있다.

뭔가 부족하다면 커스텀 하면 될거 같다.

SQLInjectionSafe

참고자료

https://lng1982.tistory.com/246

https://m.blog.naver.com/PostView.nhn?blogId=blogpyh&logNo=220675109307&proxyReferer=https%3A%2F%2Fwww.google.com%2F

https://hackbyr0k.tistory.com/2

들어가기

음 docker는 상당히 편리한 녀석이다.

VitualBox에 설치된 Ubuntu1804에 docker를 설치 해보려한다.

솔직히 이게 재대로 될지 잘 모르지만, 일단 해보자.

별거 없다. 아래 명령어를 한줄 한줄 주욱 입력해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo apt update

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

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"

sudo apt update

apt-cache policy docker-ce

sudo apt install docker-ce

sudo systemctl status docker

참고자료

[https://blog.cosmosfarm.com/archives/248/우분투-18-04-도커-docker-설치-방법/](

들어가기

우분투에 nvm을 이용해서 node를 설치해보자.

nvm은 Node.js version manager로 여러가지 버전을 손쉽게 설치하고 거기서 원하는 버전을 골라서 사용할수 있게 해준다.

우분투 저장소에서 vnm에 필요한 패키지를 설치하자.

1
2
sudo apt-get update
sudo apt-get install build-essential libssl-dev

그리고 nvm 설치 스크립트를 다운로드 후 실행하자.

1
2
3
curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh -o install_nvm.sh 
bash install_nvm.sh
source ~/.profile

nvm 설치가 완료가 되었다면, 아래 명령어를 입력하여 설치 가능한 node.js 버전들을 확인할 수 있다.

1
nvm ls-remote

현재 LTS 버전인 10.16.2 버전을 설치해보겠다.

1
nvm install 10.16.2

설치가 끝나면

1
node -v

위 명령어로 설치된 node의 버전을 확인 할 수 있다.

추가로

1
nvm use 6.0.0

위처럼 만약 다른 버전의 node가 설치되어 있다면 버전을 바꿔서 사용할 수도 있다.

설치된 node 버전을 보려면 아래 명령어로 확인 할 수 있다.

1
nvm ls

참고자료

https://itstory.tk/entry/Ubuntu-1604-nodejs-와-npm-설치

https://zetawiki.com/wiki/우분투_curl_설치