[서버구축] 나만의 GitLab 서버를 집 공유기 뒤에 놓아보기 Computer Tip

말 그대로 집에 공유기 안에 우분투 서버 (16.04 LTS)를 구축하여 그 안에 
docker로 gitlab 서버를 설치해 보고 DDNS를 이용해 IP가 변경되어도 
자동으로 외부에서 접속 가능하도록 하였습니다.
외부에서는 SSH 터널링으로 안전하게 해당 내용을 접속하면 됩니다.

자 차근 차근 따라해 보시지요.


1) 도메인 구입

저는 가비아에서 1년에 13,000원 인가에 1년 구입하였습니다.
도메인 이름은 "softisit.com" 이라고 하였습니다.
(아래 예제에도 그와같이 등장할 것입니다)


2) 우분투 서버 설치

예전 맥미니에 우분투 12.04 데스크탑 설치 했던 적이 있는데 거의 유사합니다.
대신 이번에는 우분투 서버 16.04.2 iso 이미지를 USB로 구워 위와 유사하게 맥미니에
설치하였습니다. 최초 설치할 때 F4를 누르고 최소가장머신 설치 (JEOS : Just Enough OS)로 
설치하였고 OpenSSH 서버 만 설치된 상태입니다.
(심지어는 파이썬도 기본 설치되지 않은 상태입니다)

네트워크를 설치하고 공유기 내에서 NAT 환경에서 잘 동작하는지 확인합니다.

보통 공유기 내부에 두면 DHCP로 IP를 받아오는데,
그것을 고정으로 설정해 두면 됩니다.
제대로 설정하려면 공유기에서 네트워크 주소 대역의 DHCP 설정 대역을 정하고
그 대역 밖에 설정을 하면 됩니다.

실 예에서 공유기의 사설 주소는 192.168.0.1 이고,
서버의 주소는 192.168.0.73 으로 설정한 상태입니다.


3) SSH 접속 및 22번 포트 포워딩 설정

일단 192.168.0.x 다른 곳에서 192.168.0.73으로 SSH를 해 보고 접속이 잘 되는 지 확인합니다.
제일 최초에는 암호 접속을 하였지만, 그 다음에는 키로 접속하도록 하기 위하여
접속할 호스트 (맥이나 리눅스)의 ~/.ssh/id_rsa.pub 의 내용을 SSH 서버 (..73)의
~/.ssh/authorized_keys 에 추가해 줍니다.
(만약 안되면 chmod g-w ~/.ssh/authorized_keys 라는 그룹 쓰기 권한을 삭제해 줍니다)
이제 잘 되면 원격 SSH 서버는 암호로는 아예 접속하지 못하도록 해줍니다.

73 서버에서

$ sudo vi /etc/ssh/sshd_config
를 열어
PasswordAuthentication no

및 

UseDNS no

를 설정했는지 확인합니다.

그 다음 ssh 서비스를 재기동시킵니다.

$ sudo service ssh restart


(UseDNS no 를 설정하는 이유를 참고하십시오)

그러면 앞으로는 암호로 접속은 불가능하고 
키를 가진 경우에만 접속가능합니다.

2006년 경 레드햇 엔터프라이즈 리눅스를 이용하여
프로젝트를 하는데 외부에서 암호에 대한 사전공격으로
해킹을 당한 이후 부터는 꼭 이렇게 암호를 막고 키로만 
접속 가능하게 해 놓습니다.

(AWS EC2 등도 모두 같은 방식으로 접속을 허용합니다)

이제는 공유기를 열어 외부에서 접속되는 연결 중,
22번 (SSH) 접속 포트를 내부 서버 (192.168.0.73)으로 
포트 포워딩 시킵니다.
(각 공유기별 포트포워딩 방법은 쉽게 찾을 수 있습니다)


4) Docker 설치 및 GitLab 설치

최근 (2017년도)에 Docker가 CE (Community Edition) 및 EE (Enterprise Edition)으로
분리 되었습니다. 물론 CE 버전을 설치합니다.

docker-compose 는 한 개 이상의 컨테이너를 유기적으로 연동하여 
사용할 수 있는 것이라 생각하시면 됩니다.

지난해에 gitlab을 docker로 설치하려고 하였는데 
어려움이 많아 직접 설치를 해 놓고 사용한 적이 있습니다.

그러나가 최근 방법을 알고 부터는
'아!!! 역시 이래서 컨테이너가 좋구나...!!!' 를
실제 체감합니다.

다커를 이용하여 gitlab을 설치하는 방법은 여러가지 존재하지만
다음의 sameersbn/gitlab 중, docker-compose를 이용하였습니다.

뭐 읽으실 필요도 없이 다음과 같이 하면 됩니다.

우선 설치할 머신 (..73) 에서 docker 가 동작하고 있고,
sudo 명령없이 잘 docker가 수행되도록 한 상태라 가정합니다.
(위의 링크대로 docker를 그룹에 추가하고 reboot 합니다)

그리고 다음과 같이 /dhv (docker host volume) 을 추가해 봅니다.

$ sudo mkdir -p /dhv

이제 다음과 같은 

$ cat docker-compose.yml
version: '2'

services:
  redis:
    restart: always
    image: sameersbn/redis:latest
    command:
    - --loglevel warning
    volumes:
    - /dhv/gitlab/redis:/var/lib/redis:Z

  postgresql:
    restart: always
    image: sameersbn/postgresql:9.6-2
    volumes:
    - /dhv/gitlab/postgresql:/var/lib/postgresql:Z
    environment:
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production
    - DB_EXTENSION=pg_trgm

  gitlab:
    restart: always
    image: sameersbn/gitlab:9.0.0
    depends_on:
    - redis
    - postgresql
    ports:
    - "10080:80"
    - "10022:22"
    volumes:
    - /dhv/gitlab/gitlab:/home/git/data:Z
    environment:
    - DEBUG=false

    - DB_ADAPTER=postgresql
    - DB_HOST=postgresql
    - DB_PORT=5432
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production

    - REDIS_HOST=redis
    - REDIS_PORT=6379

    - TZ=Asia/Kolkata
    - GITLAB_TIMEZONE=Kolkata

    - GITLAB_HTTPS=false
    - SSL_SELF_SIGNED=false

    - GITLAB_HOST=localhost
    - GITLAB_PORT=10080
    - GITLAB_SSH_PORT=10022
    - GITLAB_RELATIVE_URL_ROOT=
    - GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphanumeric-string
    - GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
    - GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alphanumeric-string

    - GITLAB_ROOT_PASSWORD=
    - GITLAB_ROOT_EMAIL=

    - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
    - GITLAB_NOTIFY_PUSHER=false

    - GITLAB_EMAIL=notifications@example.com
    - GITLAB_EMAIL_REPLY_TO=noreply@example.com
    - GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com

    - GITLAB_BACKUP_SCHEDULE=daily
    - GITLAB_BACKUP_TIME=01:00

    - SMTP_ENABLED=false
    - SMTP_DOMAIN=www.example.com
    - SMTP_HOST=smtp.gmail.com
    - SMTP_PORT=587
    - SMTP_USER=mailer@example.com
    - SMTP_PASS=password
    - SMTP_STARTTLS=true
    - SMTP_AUTHENTICATION=login

    - IMAP_ENABLED=false
    - IMAP_HOST=imap.gmail.com
    - IMAP_PORT=993
    - IMAP_USER=mailer@example.com
    - IMAP_PASS=password
    - IMAP_SSL=true
    - IMAP_STARTTLS=false

    - OAUTH_ENABLED=false
    - OAUTH_AUTO_SIGN_IN_WITH_PROVIDER=
    - OAUTH_ALLOW_SSO=
    - OAUTH_BLOCK_AUTO_CREATED_USERS=true
    - OAUTH_AUTO_LINK_LDAP_USER=false
    - OAUTH_AUTO_LINK_SAML_USER=false
    - OAUTH_EXTERNAL_PROVIDERS=

    - OAUTH_CAS3_LABEL=cas3
    - OAUTH_CAS3_SERVER=
    - OAUTH_CAS3_DISABLE_SSL_VERIFICATION=false
    - OAUTH_CAS3_LOGIN_URL=/cas/login
    - OAUTH_CAS3_VALIDATE_URL=/cas/p3/serviceValidate
    - OAUTH_CAS3_LOGOUT_URL=/cas/logout

    - OAUTH_GOOGLE_API_KEY=
    - OAUTH_GOOGLE_APP_SECRET=
    - OAUTH_GOOGLE_RESTRICT_DOMAIN=

    - OAUTH_FACEBOOK_API_KEY=
    - OAUTH_FACEBOOK_APP_SECRET=

    - OAUTH_TWITTER_API_KEY=
    - OAUTH_TWITTER_APP_SECRET=

    - OAUTH_GITHUB_API_KEY=
    - OAUTH_GITHUB_APP_SECRET=
    - OAUTH_GITHUB_URL=
    - OAUTH_GITHUB_VERIFY_SSL=

    - OAUTH_GITLAB_API_KEY=
    - OAUTH_GITLAB_APP_SECRET=

    - OAUTH_BITBUCKET_API_KEY=
    - OAUTH_BITBUCKET_APP_SECRET=

    - OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL=
    - OAUTH_SAML_IDP_CERT_FINGERPRINT=
    - OAUTH_SAML_IDP_SSO_TARGET_URL=
    - OAUTH_SAML_ISSUER=
    - OAUTH_SAML_LABEL="Our SAML Provider"
    - OAUTH_SAML_NAME_IDENTIFIER_FORMAT=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
    - OAUTH_SAML_GROUPS_ATTRIBUTE=
    - OAUTH_SAML_EXTERNAL_GROUPS=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME=

    - OAUTH_CROWD_SERVER_URL=
    - OAUTH_CROWD_APP_NAME=
    - OAUTH_CROWD_APP_PASSWORD=

    - OAUTH_AUTH0_CLIENT_ID=
    - OAUTH_AUTH0_CLIENT_SECRET=
    - OAUTH_AUTH0_DOMAIN=

    - OAUTH_AZURE_API_KEY=
    - OAUTH_AZURE_API_SECRET=
    - OAUTH_AZURE_TENANT_ID=

파일 (docker-compose.txt <다운로드 후 txt 확장자를 yml 로 수정합니다>) 을 저장하여,

다음의 명령으로 간단하게 설치 및 동작합니다.

$ docker-compose up

그러면 필요한 컨테이너 이미지를 다운로드하고 마법같이 실행되는 것을 

내부에 다른 곳에서

http://192.168.0.73:10080 주소로 들어가 보면 확인 가능합니다.

최초에 관리자 암호를 설정하라는 창이 나타나며,

다음에 로그인 하여

root 계정으로 들어가 최초 설정한 암호로 로그인 (Sign in) 합니다.

다음에는 우측 상단의

관리자 메뉴로 들어가
새로운 자신의 사용자를 관리자 권한으로 추가합니다.

그리고 생성한 사용자 ID로 로그인 합니다.

이후 그룹을 먼저 만들고 그룹안에 프로젝트를 생성하고 기존 git을 가져오던가
또는 특정 폴더의 내용을 git에 올릴 수 있습니다.

기타 작업은 일반 gitlab 과 동일합니다.


5) 도메인 및 DDNS 및 SSH 터널링을 통한 원격 접속

지금까지의 작업에 비해서 다음의 작업은 조금 더 복잡할 수 있지만 잘 따라하시면 
어렵지 않게 작업할 수 있습니다.


우선, 가비아 도메인 검색을 통하여 도메인을 등록합니다.
모든 설정은 기본으로 해 놓습니다.

저는 softisit.com 이라는 도메인을 구입하였으므로 해당 이름으로 도메인이 설정되어 있다고 가정합니다.

구입되고 약간의 시간 (한두시간 이 안 걸리는 시간)이 흐른 후 바로 등록이 되었습니다.

만약 고정 IP를 가지고 있다면 상황이 쉬워지지만 일반적으로 가정에서 공유기로 사용할 경우
공유기는 DHCP로 유동 IP를 할당 받고 (하지만 인터넷 주소입니다) 그 안에 공유기에 접속하는
호스트 또는 핸드폰 인터넷 전화 등 모든 기기들 역시 사설망 (보거스 주소대역 예, 192.168.0.x)에서
DHCP로 주소를 받아 (또는 위에서와 같이 .73 으로 붙박이 설정) 사용합니다.

일반적인 상황에서는 NAT 뒤에 있는 곳에서 먼저 외부 인터넷으로 접속을 가능하지만
외부에서 내부로 들어오지는 못합니다. 다만 위에서와 같이 TCP 22번 (SSH) 포트를
포트 포워딩 한 경우 위의 공유기 인터넷 주소의 22번 TCP 포트로 연결 시도를 할 경우
내부에 연결된 서버가 대신 받게 되는 구조입니다.

외부에서 접속할 때 또 한가지 염두에 두어야 하는 것은 공유기의 주소 자체가 언제 바뀔 지 모르기 때문에
(일반적으로 그렇게 자주 변경되지는 않습니다만...) 위에 구입한 도메인 (예로 softisit.com) 에 대당
IP 주소가 자동으로 매핑되도록 하는 DDNS (Dynamic DNS) 기능을 이용하면 됩니다.

쉽게 이야기하여 ..73 서버 입장에서 공유기의 위부 IP 주소가 이전과 변경되었을 때
해당 IP를 DDNS 서버에 다시 설정하여 외부에서 해당 도메인으로 접속하면
변경된 IP를 몰라도 자동으로 접속 가능해 진다는 시나리오 입니다.

위와 같이 DDNS 서비스를 하는 곳이 여럿 있으나 공짜로 API를 이용하여 사용할 수 있는
곳 중에 한 곳으로 선택한 것이 바로,

클라우드플레어 라는 서비스 입니다.

사용자를 만들고 ... 새로운 도메인을 넣고 디폴트로 진행을 하다가...

위와 같이 1,2차 DNS 서버를 수정하라고 나옵니다. (hhncc.com 대신 자신의 도메인이 보일겁니다)

그리고 다시 도메인을 구입하였던 가비에서 해당 내용으로 DNS 서버를 설정합니다.

그리고 상단에 왼쪽에서 세번 째 DNS 를 눌러,

위와 같이 mm.softisit.com 이라는 호스트가 특정 IP를 가리키도록 합니다. 회색의 구름 위로 넘어가는 아이콘 표시가 되게 하여
HTTP PROXY 를 제외하고 DNS resolve 만 하도록 지정해 놓습니다. (그렇게 해야 제대로 서비스가 되었습니다.)

이제는 마지막으로 ..73 서버에서 주기적으로 IP 주소를 체크하여 변경된 경우 위의 DNS IP 주소를 변경하도록 해 보겠습니다.

​$ cat get.sh
#!/bin/bash

DOMAIN_NAME="softisit.com" # 자신의 도메인 설정
EMAIL="mcchae@gmail.com" # 자신의 접속 이메일 주소
API_KEY="38de---------------------8ba6" # cloudflare의 오버뷰>API 키 구하기>글로벌 API 키 보기 에서 구함
curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN_NAME}" \
        -H "X-Auth-Email: ${EMAIL}" \
        -H "X-Auth-Key: ${API_KEY}" \
        -H "Content-Type: application/json"

ZONE_ID="545a8-------------------add7" # 위의 명령으로 나온 결과의 ZONE ID
RECORD_NAME="mm.softisit.com" # 설정하고픈 호스트명
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${RECORD_NAME}" \
        -H "X-Auth-Email: ${EMAIL}" \
        -H "X-Auth-Key: ${API_KEY}" \
        -H "Content-Type: application/json"

위의 명령을 수행하여 필요한 결과를 얻어 옵니다.

$ bash -x ./get.sh
+ DOMAIN_NAME=softisit.com
+ EMAIL=mcchae@gmail.com
+ API_KEY=38de00-------------------3a8ba6
+ curl -s -X GET 'https://api.cloudflare.com/client/v4/zones?name=softisit.com' -H 'X-Auth-Email: mcchae@gmail.com' -H 'X-Auth-Key: 38de------------------5f3a8ba6' -H 'Content-Type: application/json'
{"result":[{"id":"545a------------------add7","name":"softisit.com","status":"active","paused":false,"type":"full","development_mode":0,"name_servers":["jay.ns.cloudflare.com","lara.ns.cloudflare.com"],"original_name_servers":["ns.gabia.co.kr","ns.gabia.net","ns1.gabia.co.kr"],"original_registrar":null,"original_dnshost":null,"modified_on":"2017-04-10T14:57:30.290291Z","created_on":"2017-03-31T11:41:04.566152Z","meta":{"step":4,"wildcard_proxiable":false,"custom_certificate_quota":0,"page_rule_quota":3,"phishing_detected":false,"multiple_railguns_allowed":false},"owner":{"type":"user","id":"ddd51c7b9cdc98c273270461a083a4b4","email":"mcchae@gmail.com"},"permissions":["#analytics:read","#billing:edit","#billing:read","#cache_purge:edit","#dns_records:edit","#dns_records:read","#lb:edit","#lb:read","#logs:read","#organization:edit","#organization:read","#ssl:edit","#ssl:read","#waf:edit","#waf:read","#zone:edit","#zone:read","#zone_settings:edit","#zone_settings:read"],"plan":{"id":"0feeeeeeeeeeeeeeeeeeeeeeeeeeeeee","name":"Free Website","price":0,"currency":"USD","frequency":"","is_subscribed":true,"can_subscribe":false,"legacy_id":"free","legacy_discount":false,"externally_managed":false}}],"result_info":{"page":1,"per_page":20,"total_pages":1,"count":1,"total_count":1},"success":true,"errors":[],"messages":[]}+ ZONE_ID=545a849a3eae142fd44d6a106009add7
+ RECORD_NAME=mm.softisit.com
+ curl -s -X GET 'https://api.cloudflare.com/client/v4/zones/545a8---------------------09add7/dns_records?name=mm.softisit.com' -H 'X-Auth-Email: mcchae@gmail.com' -H 'X-Auth-Key: 38de00-------------------a8ba6' -H 'Content-Type: application/json'
{"result":[{"id":"ff5f89----------------------5ad73","type":"A","name":"mm.softisit.com","content":"180.-----.215","proxiable":true,"proxied":false,"ttl":1,"locked":false,"zone_id":"545a849a3eae142fd44d6a106009add7","zone_name":"softisit.com","modified_on":"2017-04-10T14:57:30.290291Z","created_on":"2017-04-10T14:57:30.290291Z","meta":{"auto_added":false}}],"result_info":{"page":1,"per_page":20,"total_pages":1,"count":1,"total_count":1},"success":true,"errors":[],"messages":[]}

$ cat set.sh
#!/bin/bash
#NEW_IP=`wget -O - -q http://ifconfig.me/ip`
NEW_IP=`curl -q ifconfig.co`
CURRENT_IP=`cat /var/tmp/current_ip.txt`

if [ "$NEW_IP" = "$CURRENT_IP" ]
then
        echo "No Change in IP Adddress"
else
        ZONE_ID="545a--------------------9add7" # get.sh 첫번째 curl 에서 얻은 값
        RECORD_ID="ff5f8----------------------5ad73" # get.sh 두번째 curl 에서 얻은 값
        EMAIL="mcchae@gmail.com"  # 자신의 접속 이메일 주소
        API_KEY="38de0---------------------a8ba6" # cloudflare의 오버뷰>API 키 구하기>글로벌 API 키 보기 에서 구함
        RECORD_NAME="mm.softisit.com" # 설정하고픈 호스트명

        curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
          -H "X-Auth-Email: ${EMAIL}" \
          -H "X-Auth-Key: ${API_KEY}" \
          -H "Content-Type: application/json" \
          --data "{\"id\":\"${ZONE_ID}\",\"type\":\"A\",\"name\":\"${RECORD_NAME}\",\"content\":\"${NEW_IP}\"}"

        echo $NEW_IP > /var/tmp/current_ip.txt
fi


이제는 위의 set.sh 를 주기적으로 실행시키기 위하여 crontab에 넣습니다.

$ crontab -e
*/15 * * * * /path/to/set.sh 2>/tmp/ddns.err >/tmp/ddns.out

그러면 매 15분 마다 IP 변경을 체크하고 변경되었다면 변경된 IP로 DNS 서버에 알려줍니다.
(변경된 것은 바로 적용이 되었습니다)

참고로 gitlab에서 알아서 백업을 특정위치에 받는데 이것이 금방 디스크를 full 나게 만들어
수정해보았습니다.

$ cat /path/to/cron_gitlab.sh
#!/bin/bash

GL_ORG=/dhv/gitlab/gitlab/backups
GL_BKUP=/home/mcchae/bkup/gitlab

mv -f $GL_ORG/*.tar $GL_BKUP 2>/dev/null
find $GL_BKUP -mtime +2 -type f -delete

그리고 crontab에 추가합니다.

$ crontab -e
0 7 * * * /path/to/cron_gitlab.sh 2>/tmp/cron_gitlab.err >/tmp/cron_gitlab.out

새벽 5시엔가 주기적으로 백업 스크립트가 도는 것 같아,
일부터 새벽 7시로 해 놓았습니다.


6) 원격 접속

인터넷 어디에서든 맥으로 접속하는데,

$ cat ~/.ssh/config
Host mm
        User    mcchae  # 원격 접속할 계정 명
        HostName        mm.softisit.com  # DDNS 에 설정한 호스트명
        ServerAliveInterval 30
        ServerAliveCountMax 3
        # localhost:10080  => 10080 (gitlab 서버의 웹 서비스)
        LocalForward   10080   localhost:10080
        # localhost:10022  => 10022 (gitlab 서버의 SSH 서비스)
        LocalForward   10022   localhost:10022

위와 같이 설정해 놓고,
터미널에서 

$ ssh mm
으로 접속해서 접속이 일단 성공해야 합니다.
(암호 없이 접속하도록 이미 설정되었습니다)

이 상태에서 맥의 웹브라우저에서

http://localhost:10080

으로 접속하면 
'빙고!'


어느 분께는 도움이 되셨기를...

덧글

댓글 입력 영역

구글애드텍스트