현상

  • 예전에 만들어뒀던 레거시 어플리케이션이 있는데, 그냥 놀려두기보다는 그냥 시험삼아 싱글 클러스터에 배포해보기로 했다.
  • 그리하여 성공적으로 배포가 되었었는데, 평상시와 같이 컴퓨터를 켜고 Lens를 보자마자 이런 에러가 올라와있었다.

  • Probe가 보고있는 5000포트에 대해서 GET 응답을 받아올 수 없어 컨테이너가 죽었다고 판단한 것이다.

해결

  • 소스코드를 보니 서버가 5000 포트가 아니라 8080포트에서 돌아가고 있었다.
  • 하지만 난 8080포트를 거의 사용하지 않기 때문에(Python을 주력으로 사용하는 지금에 와서야 아무런 상관이 없는 이야기지만, 과거 Java로 개발공부를 할때 Tomcat WAS와의 포트 충돌 때문에...) 내가 8080으로 서버를 돌렸을 리가 없는데.


  • 어쨌건 사건은 터졌고, 고쳐줘야겠지.
  • 그래서 간단하게 서버 포트를 8080에서 5000으로 옮겨줬다.

  • 그랬더니 이번엔 버전이 말썽이다...
  • 귀찮아서 requirements에 버전 명시를 안해준 탓인가보다. 어쨌건 저 안내를 따라 버전을 맞춰주니 무사히 배포가 끝났다.

오늘의 교훈

  • requirements에는 반드시 버전을 명시하도록 하자.
  • 어쩔땐 열받으면서도 이럴땐 pom.xml파일이 그리워지기도 한다...

'DevOps > kubernetes' 카테고리의 다른 글

[Helm] helm이란?? - 1  (0) 2021.10.27

NAS로 gitlab-postgres 데이터 이전 중 DB 기동 실패

개요

  • 기존 Node1에서 돌던 Redis 및 Node2에서 돌던 PostgreSQL(이하 PG), gitlab의 데이터를 NAS로 이전

  • Redis의 경우 4.0.7 버전을 사용중에 중지시키고, 예전에 사용했던 YAML파일을 복사해 붙여넣기 하다가 3.0.7 버전으로 사용하는 바람에 개발규칙-참고 페이지가 불능이 되었던 사태가 있었음 → redis 버전을 제대로 되돌려주니 해결됨

  • PG의 경우 기존 Deployment, replica=1으로 실행되던 것을 replica=2로 HA구성을 위해 바꾸었으나 조부장님 컴퓨터에서 깃랩이 자주 팅기는 현상이 발생함(난 안그랬음)

  • 그래서 Deployment를 StatefulSet으로 변경하고 replica=2를 주었더니 CrashLoopBack과 함께 컨테이너가 시작되지를 않음.

PANIC: could not locate a valid checkpoint record

해결

  • 검색 결과, 해당 오류는 PG의 로그 시스템이 꼬여서 생기는 오류라고 한다. 그래서 강제로 로그 시스템을 리셋시켜줘야 하는데, 리셋하는 방법은 다음과 같다.
su postgres # root으로 실행할 수 없음
pg_resetwal -f /var/lib/postgresql/data # DATADIR로 명명된 데이터 디렉토리를 지정하면 되는데, 깃랩의 경우 이 위치를 데이터 디렉토리를 잡고 있었음
  • 그러나 컨테이너가 계속 생성과 동시에 폭파되므로, deployment에 args 인자로 강제로 sleep을 전달
    spec.template.spec.containers[0].args: ["sleep","1000000"]
  • 이후 상단의 명령어를 따라 로그를 리셋해주고 파드를 삭제해주면 Deployment가 알아서 파드를 생성하면서 정상적으로 기동

미해결

  • 결국 튕기는 현상은 아직 복구하지 못함

21. 08. 17

개요

  • 튕기는 현상에 대하여 NAS의 네트워크 상태를 모니터링한 결과, 초당 12Mbps로 어딘가에서 데이터를 읽어들이고 있음.

  • 타 서버의 경우 기가급 네트워크를 사용하고 있는데 반해, node2:251서버의 경우 메인보드에 랜카드를 꽂을 슬롯이 부족해 기본 랜카드를 사용하고 있어 100Mbps급 네트워크를 사용하고 있는 것으로 추정됨

  • 네트워크 최대 제한에 걸려서 더이상 데이터를 보내지 못하고 트래픽 과부하로 인해 깃랩이 빈번하게 튕기는 것으로 추정

해결

  • NAS로 이전했던 postgre 데이터를 다시 node2로 재이전하고 HA 계획을 보류

'DevOps > gitlab' 카테고리의 다른 글

[DevOps] Gitlab으로 알아보는 DevOps의 이해 - 1  (0) 2021.09.26
[Node.js] Cannot get / 에러 해결  (0) 2021.09.10

docker-compose?

쉽게 말하면, 컨테이너 여러개를 하나의 파일로 띄울 수 있는 파일이다. 더불어 볼륨, 네트워크, 서비스 구성 역시 이 파일 내부에서 모두 설정 가능하다.

env_file vs environment

컴포즈는 각 컨테이너에 환경변수를 두 종류로 전달할 수 있다. 바로 env_file과 environment인데, 최근 컨테이너를 기반으로 hadoop 클러스터를 구축하다가 신기한 경험을 했다.

총 두개의 컨테이너(service: hadoop, spring)를 띄우고, 하나의 컨테이너에는 stand-alone 하둡 클러스터를, 다른 쪽에는 Spring 프로젝트를 띄워 HDFS를 트리구조로 보여주는 어플리케이션을 만들었다.

services.hadoop.environment 태그로 hadoop 서비스에는 JAVA_HOME 및 기타 하둡 환경변수를 전달해주었고, sshd_config 파일 및 ssh_config 파일에서 No-password ssh 접속에 필요한 설정도 모두 맞춰주었다. 그런데 자꾸 Datanode와 NodeManager, SecondaryNamenode가 기동에 실패했다. 원인은 JAVA_HOME, HADOOP_HOME, YARN_HOME을 찾을수 없다고.

아니 그런데 인자는 분명히 전달을 해 주었는데... 뭔가 이상해서 실행 명령을 tail -f /dev/null로 변경해주고 직접 컨테이너 내부로 들어가 확인해봤다. 처음 접속햇을 때는 분명 환경변수들이 등록된 상태였는데, ssh localhost로 다시 동일한 컨테이너에 재접속을 해보니 밖에서 설정해둔 온갖 환경변수가 등록되지 않은 상태였다.

뭔가 이상해서 계속 추적해본 결과, 이전의 테스트 환경(동일한 구조로 몇번 다르게 빌드해본 적이 있다)과 달랐던 것은 딱 하나.

바로 환경변수를 전달하는 태그가 env_file에서 environment로 바뀌었다는 것이다.

그래서 이번엔 env_file로 바꾸어줬더니, 정상적으로 클러스터는 기동이 완료가 되었다.
(물론, 각 노드들의 data.dir을 HDFS 내부가 아닌 로컬로 잡고 볼륨을 바인딩해주면, cannot stat 에러와 함께 컨테이너가 정상 실행이 되지 않는 버그는 여전하다)

그래서 찾아보았더니, 이미 나와 같은 의문을 겪은 사람들이 많이 있었다.
그래서 결론은 이렇다.

  • env_file의 경우, 이미지에 환경 변수를 세팅한다. 즉, ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64가 되는것.
  • 그에 비해 environment의 경우 컨테이너에 환경 변수를 전달한다. 즉, 현재 기동되고 있는 쉘에 전달되므로 시스템이 재시작 되기 전에는 적용한 환경 변수가 새로운 접속에 대해서는 적용되지 않는다는 것. 하지만 하둡은 SSH 데몬을 기반으로 통신하므로, 기존 Bash(환경변수 적용)가 아닌, 새로운 Bash(환경변수 미적용)쉘에서 명령이 실행되므로 각종 환경변수를 인지하지 못했던 것.

결국 그냥 파일을 하나 더 만들기 귀찮아서, vim에서 대충 후다닥 만들고 테스트에 눈이 멀었던 내 실수였다. 오늘도 교훈을 얻었다...

Helm?

  • Helm은 투구[tu:gu]다.
  • Kubernetes cluster 내부에 어플리케이션을 자동으로 빌드 및 배포할 수 있도록 돕는 모듈
  • Server/Client 구조(Helm 2.x), 혹은 Library/Client 구조(Helm 3.x)형태를 취하고 있음
  • 3.x 버전으로 업그레이드되며 쿠버네티스 외에 다른 클러스터에도 서서히 지원이 진행되는 중.

Helm의 구조

  • Helm 2.x
    • 전통적인 헬름 구조로, Server(Helm) + Client(Tiller)의 구조로 되어있다.
    • Helm : 헬름 아카이브 서버와 실제 어플리케이션이 설치될 Pod/Deployment(container)를 연결해주는 역할을 하며, Stable/Incubator repository를 구독한다.
    • Tiller : 실제 어플리케이션이 설치될 위치의 컨테이너에 설치되며 구독한 repository(stable/incubator) 레포지토리로부터 요청한 이미지를 helm 서버로부터 받아와 chart 설치를 진행한다. 2.x버전에만 존재하며 3.x 버전부터는 삭제되어 helm에 통합되었다.
  • Helm 3.x
    • Tiller가 Deprecated되고 , Stable 및 Incubator 단 두개만 존재하던 공식 repository가 더욱 확장되어 github과 같은 오픈 저장소로 변경되었다.
    • 기존의 stable 및 incubator 형태의 레포지토리도 천천히 이관되고 있으나, 대부분이 개발 중단 상태이며 이관 역시 매우 느리게 진행되고 있어 사용을 권장하지 않고, artifacthub.io로부터 bitnami, bitnami-common 등 최신 버전의 차트를 사용할 것을 권장하고 있다.
    • 기존의 Tiller와 Helm이 하던 일을 합쳐놓았으며, 컨테이너 측에는 Helm 바이너리만 설치해 원하는 repository 주소를 추가하고 해당 repository의 차트를 가져와 install 하는 것으로 차트 설치가 끝난다.

Helm Chart?

  • K8s에 어플리케이션을 손쉽게 배포하기 위한 도구
  • 어플리케이션 배포를 자동화하기 위한 도구(CI: 지속적 통합)

Chart structure(v3.7 기준)

.
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml
  • Chart.yaml
    헬름 차트에 대한 전반적인 내용을 기록한다. apiVersion, name, version의 경우는 필수 항목이고, 외에 description등의 내용을 추가할 수 있다.
  • charts/
    해당 차트에 타 차트에 종속성을 가지는 것이 있다면 해당 종속성을 삽입해주는 차트. maven에서 pom.xml의 dependencies와 비슷한 역할을 한다고 보면 된다. 만약 어플리케이션이 배포되는 환경이 외부 인터넷(이더넷) 사용이 불가능한 폐쇄 환경이라면, 차트에서 요구하는 종속성을 해당 폴더 내에 위치하도록 해야 한다.
  • templates/
    해당 헬름의 차트의 설치, 즉 helm install명령어와 함께 클러스터에 배포될 쿠버네티스 리소스를 모아놓은 폴더이다. 해당 폴더에 들어있는 친구들이 차트의 설치와 함께 우루루 쏟아져나와 배포된다. 실제로 열어보면 헬름 문법(이라고는 하지만 사실은 golang의 문법을 살짝 바꿔놓은 것)에 맞춰 리소스들의 값 일부가 values.yaml, Chart.yaml등을 참조하게 되어있다.
  • values.yaml
    templates/ 내부에 있는 k8s 리소스들의 빈 값을 채워주는 일종의 환경 파일. 리소스 파일에서 빵꾸난 값은 여기서 채워넣을 수 있다.

'DevOps > kubernetes' 카테고리의 다른 글

[troubleshooting] readiness/liveness probe failed  (0) 2022.01.13

또 안된다.

DNS 는 제대로 구글 DNS를 보고 있고, 컨테이너의 resolv.conf도 구글 DNS를 보고 있는데 안된다.

  • 문제의 서버에서 본 dockerd의 실행 커맨드

    $ root@dev-server:~# ps -ef
    root         761       1  0 10월06 ?      00:06:25 /usr/bin/dockerd --iptables=false --exec-opt native.cgroupdriver=systemd --data-root=/var/lib/docker --log-opt max-size=50m --log-opt max-file=5 --dns 8.8.8.8 --dns 8.8.4.4 --dns-search default.svc.cluster.local --dns-search svc.cluster.local --dns-opt ndots:2 --dns-opt timeout:2 --dns-opt attempts:2
  • 정상적인 서버에서 본 dockerd의 실행 커맨드

    root@dev201022:/etc/docker# ps -ef|grep docker
    root      1262     1  0  6월14 ?      01:17:03 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
    • 실행 명령이 다름. 기존 k8s를 설치하고 지우는 과정에서 깔끔하지 못해 기존 snap docker에 서비스 및 소켓이 묶여있다.

수동으로 실행시킨 dockerd vs init.d 서비스로 실행된 dockerd

  • 명백한차이가 있다. 나는 이 차이를 docker 서비스의 잘못으로 생각하고 이슈해결에 들어갔다.

  • dockerd와 docker.socket을 중지

    $ root@dev-server:/etc/systemd/system# systemctl stop docker
    $ root@dev-server:/etc/systemd/system# systemctl stop docker.socket
  • systemd에 바인드된 docker service를 지워준다.

    $ root@dev-server:/etc/systemd/system# rm -rf docker.service docker.service.d
  • 도커 데몬을 재시작한다

    $ root@dev-server:/etc/systemd/system# systemctl daemon-reload
  • dockerd와 docker.socket을 init에 등록

    $ root@dev-server:/etc/systemd/system# systemctl enable docker
    $ root@dev-server:/etc/systemd/system# systemctl enable docker.socket
  • dockerd와 docker.socket을 재시작

    $ root@dev-server:/etc/systemd/system# systemctl restart docker.socket
    $ root@dev-server:/etc/systemd/system# systemctl restart docker
  • docker 가동 체크

    $ root@dev-server:/etc/systemd/system# docker version
    $ root@dev-server:/etc/systemd/system# ps -ef|grep docker
  • 안될 경우 데몬을 다시 리로드해주고 도커를 재시작한다

    $ root@dev-server:/etc/systemd/system# systemctl daemon-reload
    $ root@dev-server:/etc/systemd/system# systemctl restart docker

분석

  • 솔직히 이정도쯤 되면 일반적인 해결방법이 아니다. 굉장히 특수한 케이스의 경우에 해당한다. 직접 소켓에 데몬까지 건드리는 Normal한 해결방법이 대체 어디있냐고...
  • 원인을 나열해보자면 다음과 같다.
    • 기존 snap 및 kubernetes runtime으로 이용되던 도커(1.16버전 클러스터이므로 아직 docker가 공식 runtime이었을 때다)가 kubernetes의 불완전한 제거로 인해 kubernetes DNS(10.x.x.x, 127.0.0.53)를 바라보고 있었지만, 클러스터의 핵심인 API서버가 제거되면서 기존 DNS를 바라보고 있는 docker의 요청을 받아줄 네트워크가 사라진 상태여서 외부 인터넷으로의 ping 및 패키지 업데이트가 불가능
    • DNS를 기존으로 다시 잡아주었으나 재부팅을 진행하지 않았고, 그래서 역시 불완전하게 변경된 도커 데몬이 기적적으로 제대로 인터넷 주소를 잡고 돌아가던 와중, 컴퓨터를 재부팅할 일이 생겨 재부팅
    • 재부팅 후 꼬여있는 채로 어찌저찌 돌아가던 데몬과 소켓이 꼬여버리며 사태가 발생.
    • kubernetes의 불완전 제거로 systemd로 권한이 넘어간 도커가 재부팅 이후 기존의 containerd로 다시 런타임이 되돌아갔지만, 따로 중지해주지 않아 여전히 열려있던 docker socket이 예전 systemd 예하의 socket을 보고 있음
    • 당연히 daemon이 보고있는 위치와 socket이 보고잇는 위치가 달라지니 도커는 구동중인데 이미지고 컨테이너고 아무것도 불러오지 못하는 disconnect 상태가 됨
    • 그래서 기존의 소켓 서비스도 중지 후, 다시 init 시스템에 등록 및 재시작해 제대로 containerd로 권한을 넘김
    • 문제 해결!
    • 하지만 내가 알고있는 이 해결 방법 분석이 맞는건지 모르겠다. 왠지 이번에도 어쩌다가 운좋게 해결된것같아 서버를 몇번씩이나 재부팅해봤지만 이상없었다. 일단 당장의 문제는 해결되었으나 좀 더 공부를 한 후에 다시 분석해보는 것이 좋을것같다...

전 포스트에서는 단순히 daemon.json 파일을 이용해 도커 데몬의 DNS를 고정시키는 방식을 진행했다. 하지만 지금 이 포스트를 보고있다는 건, 해당 방식으로는 해결이 되지 않았다는 뜻일 것이다.

일단, 상세한 트러블슈팅을 위해 당시 서버의 상황을 서술한다.

  • 개발 서버(이하 서버)를 모종의 이유로 standalone 쿠버네티스 클러스터로 구동해야 할 이유가 있었다. 그것도 호스트 네트워크로 운영중인 Ubuntu18.04 컨테이너 위에서.
  • 해당 사업이 종료되고, 서버를 반환받아 쿠버네티스 클러스터를 철거하고 다시 개발용 서버로 구동시켰었는데, 어찌된 일인지 자꾸 DNS가 127.0.0.53 및 10.233.0.3을 보고 있었다.
  • DNS만 봐도 쿠버네티스 내부의 DNS를 보고있는 것이 확실해졌다. 그러나 쿠버네티스의 모듈들은 모두 철거된 이후라서 대체 어떤 녀석이 도커의 DNS를 자꾸 가로채가는지 알 수 없었다.
  • daemon.json을 추가하는 방식으로는 systemctl로 매번 도커 모듈을 재시작할때마다 DNS 중복 설정으로 인해 도커가 가동되지 못했다.
  • 해결하기엔 다른 긴급 사안이 조금 많았으므로, 데몬을 중지시키고 nohup 및 출력을 /dev/null로 리디렉션 하는 방식으로 데몬을 구동시키고, 해당 명령을 init.d/에 등록하여 사용하고 있었다.
  • 그러나 빈번하게 발생하는 DNS오류에 결국 칼을 빼들었다.

daemon.json 파일을 /etc/docker/ 폴더에 넣고 systemctl start docker 명령어를 실행시키면 아마 이런 메세지가 뜨면서 도커 서비스가 실패할 것이다.

root@dev-server:/etc/docker# systemctl restart docker
Job for docker.service failed because the control process exited with error code.
See "systemctl status docker.service" and "journalctl -xe" for details.

명령어에 따르면 journalctl -xe 명령어로 로그를 확인할 수 있는 듯 하다. 그래서 해당 명령어로 도커의 로그를 확인한다.

root@dev-server:/etc/docker# journalctl -xe
...
10월 13 12:40:50 dev-server dockerd[38432]: unable to configure the Docker daemon with file /etc/docker/daemon.json: the following directives are specified both as a flag and in the configuration file: dns: (from flag: [10.233.0.3 127.0.0.53], from file: [8.8.8.8 8.8.4.4])
...

설정 파일 및 실행 명령어의 플래그 양쪽에서 DNS를 지정하고 있는데, 이게 문제가 되는 것 같다. 그럼 둘 중 하나를 제거해주면 되는데, 설정 파일의 경우 우리가 DNS를 옳게 설정했으므로 목표로하는 DNS이므로 이게 달라져서는 안된다. 즉, 실행 명령어에 박혀있는 플래그를 바꿔줘야 한다는 뜻이다.

처음에는 도커의 설정 파일이 문제라고 생각해 config 폴더를 찾아 DNS를 확인했다. 위치는 각각 다음과 같다.

  • /etc/init/docker.conf
  • /etc/init.d/docker

그러나 해당 파일에는 모두 DNS가 8.8.8.8로 정상 세팅되었다. 여기서 혼란이 오기 시작한다. 그래서 find 명령어로 관련 파일들을 이잡듯이 뒤지다 보니 발견한 위치가 하나 있었다.

# vim /etc/systemd/system/docker.service.d/docker-dns.conf
[Service]
Environment="DOCKER_DNS_OPTIONS=\
    --dns 10.233.0.3 --dns 127.0.0.53 \ 
    --dns-search default.svc.cluster.local --dns-search svc.cluster.local \
    --dns-opt ndots:2 --dns-opt timeout:2 --dns-opt attempts:2 \
"

빙고. 정답이다. 이 녀석의 DNS를 원래의 8.8.8.88.8.4.4로 수정해준다.

# vim /etc/systemd/system/docker.service.d/docker-dns.conf
[Service]
Environment="DOCKER_DNS_OPTIONS=\
    --dns 8.8.8.8 --dns 8.8.4.4 \ 
    --dns-search default.svc.cluster.local --dns-search svc.cluster.local \
    --dns-opt ndots:2 --dns-opt timeout:2 --dns-opt attempts:2 \
"

그리고 저장하고 나오면, 아래와 같은 경고 문구가 뜬다.

Warning: The unit file, source configuration file or drop-ins of docker.service changed on disk. Run 'systemctl daemon-reload' to reload units.

설정 파일이 변경되었으니 서비스 데몬을 재시작하라는 뜻이다. 명령어를 입력해준다.

systemctl daemon-reload

이제 도커 서비스를 재시작해준다.

systemctl start docker

이제 도커가 정상적으로 실행된다.

https://github.com/gliderlabs/docker-alpine/issues/334

[Temporary error (try again later) · Issue #334 · gliderlabs/docker-alpine

I run into this issue when building a docker image on Ubuntu Host. The Same build on Windows 10 using docker-tools CLI (so technically inside a VirtualBox VM) has no issues and Docker images are bu...

github.com](https://github.com/gliderlabs/docker-alpine/issues/334)

위 링크에 따르면 컨테이너가 바라보고 있는 DNS의 이슈다. 보통 컨테이너 내부에서 외부 이더넷과 연결이 되어있는지를 확인하기 위해 구글의 오픈 DNS인 8.8.8.8, 8.8.4.4(혹은 google.com)로 핑을 날려보게 되는데, 외부 이더넷과 연결되어있지 않으면 apk update로 index 파일에서 패키지 리스트를 갱신해올 때 (temporary error)라고 뜨면서 리스트를 받아오지 못한다.

호스트 컴퓨터에서는 정상적으로 가동하는 ping google.com이 컨테이너 내부에서만 수행되지 않는다면 도커 데몬이 구글, KT, SKT등 DNS를 제공하는 업체의 오픈 DNS가 아닌, 다른 DNS를 바라보고 있는 것이다.

root@dev-server:/home/dev# ps -ef|grep docker
root         761       1  0 10월06 ?      00:06:25 /usr/bin/dockerd --iptables=false --exec-opt native.cgroupdriver=systemd --data-root=/var/lib/docker --log-opt max-size=50m --log-opt max-file=5 --dns 10.233.0.3 --dns 127.0.0.53 --dns-search default.svc.cluster.local --dns-search svc.cluster.local --dns-opt ndots:2 --dns-opt timeout:2 --dns-opt attempts:2
root        1791     761  0 10월06 ?      00:00:03 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 21855 -container-ip 172.17.0.2 -container-port 5432
root        1798     761  0 10월06 ?      00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 21855 -container-ip 172.17.0.2 -container-port 5432
root       32198     761  0 10월12 ?      00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5432 -container-ip 172.17.0.3 -container-port 5432
root       32204     761  0 10월12 ?      00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 5432 -container-ip 172.17.0.3 -container-port 5432
root       37623     761  0 11:20 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.4 -container-port 8080
root       37630     761  0 11:20 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 -container-ip 172.17.0.4 -container-port 8080
root       38104   38075  0 11:26 pts/0    00:00:00 grep --color=auto docker

root 사용자 권한으로 프로세스를 확인해보니, 아니나 다를까 dockerd가 이상한 dns를 바라보고 있다. 데몬이 올바른 DNS를 바라보게 하도록 하기 위해, 일단 도커 데몬을 중지시킨다.

root@dev-server:/home/dev# systemctl stop docker

그리고 도커가 중지되었는지 확인한다.

  • systemctl이나 service로도 확인 가능. 이쪽을 더 권장한다.
    root@dev-server:/home/dev# ps -ef|grep docker
    root       38198   38075  0 11:28 pts/0    00:00:00 grep --color=auto docker

도커가 중지되었으니, 이제 올바른 DNS를 심어준다.

# vim /etc/docker/daemon.json
{
    "dns": ["8.8.8.8", "4.4.4.4"]
}

이제 도커 서비스를 재시작한다.

root@dev-server:/home/dev# systemctl start docker

이제 아마 도커가 정상적으로 가동될 것이다. 서버가 깔끔하게 도커만 사용하는 개발 서버라면 위의 방법으로 아마 해결되었겠지만, 서버에 K8s등 다른 내부 DNS를 사용하는 경우 DNS 설정이 꼬이면서 해결이 되지 않는 경우가 있다. 이 경우는 다음 포스트에서 이어서 해결해보도록 한다.

DevOps?

  • 대부 없으, 즉 대출해줄 대부업체가 없다는 뜻
  • Dev(Development) + Ops(Operations)의 합성어로, 소프트웨어의 개발과 운영 간의 협업 및 통합을 강조하는 개발환경을 말함
  • 좁게는 애자일 기법에 기반한 개발을 의미하고, 넓은 의미에서는 어플리케이션 개발주기 전체를 일컫기도 한다
  • 개발자가 개발에만 집중할 수 있도록 개발이 끝난 어플리케이션의 빌드, 테스트, 배포, 피드백 및 개선 등 기타 운영 관련 업무 등을 핵심 업무로 삼는 이들을 DevOps 엔지니어라고 한다.

CI/CD?

  • DevOps 엔지니어의 핵심 임무로, 애자일 개발 주기를 자동화하고 각종 데이터의 수집 및 모니터링을 구성하여 이후의 개발 방향의 가이드라인을 제시, 고객에게 안정적이고 높은 신뢰도의 서비스를 제공하는 것이 목표
  • CI : Continous Integration, 즉 어플리케이션을 지속적으로 빌드 및 테스트하는 것을 의미
  • CD : Continous Delivery, 빌드된 어플리케이션을 지속적으로 배포하는 것을 의미
  • 즉, 한국어로는 지속적 통합/지속적 제공 정도의 직역이 된다.
  • 일반적으로 어플리케이션의 개발 주기인 Build-Test-Production에 기초한 파이프라인의 일종
  • 최근 B2C(Business to Consumer) 프로젝트에서 핫한 트렌드로 떠오르는 개발 프로세스인 애자일(Agile)기법에 기반을 두고 있음

대표적 CI/CD 도구?

Jenkins

  • 가장 많이 사용하고 대표적인 오픈소스 CI/CD 도구.
  • 가장 자율적이고 많은 부분을 직접 디자인 가능하지만, 반대로 말하자면 사용법을 익히기가 가장 어렵다.
  • 그러나 가장 커뮤니티가 방대해서 온갖 참고자료를 쉽게 찾을 수 있는 것은 장점.

Gitlab

  • 오픈소스 CI/CD 도구
  • Docker를 공식 실행자로 지원, 쿠버네티스와의 연동도 admin.conf 파일로 간단하게 구성가능.
  • SaaS와 PaaS 모두 제공하고 있으며, PaaS의 경우는 오픈소스, SaaS의 경우는 엔터프라이즈로 제공하고 있다.
  • Docker를 공식 executor로 제공하는데, 이때문에 도커 기반 환경(Docker swarm, K8s(<=1.20)-Gitlab)과의 상성이 아주 좋다.
  • Podman이나 Openshift를 사용하는 경우 추천하고 싶지는 않음
  • Gitlab 공식 Docs에서 예제 CI/CD 파이프라인을 제공하고 있고, 성능 역시 나쁘지 않다. helm 및 Dockerd에 대한 기초적인 이해가 있다면 가볍게 튜닝해서 사용할 수도 있다.

기타 다른 CI/CD Tools

  • Travis
  • CircleCI
  • Bamboo
  • Github action
  • Chef
    • 위 도구들은 내가 미처 사용해보지 못했거나, 어딘가에 리뷰할 만큼 충분히 써보지는 못했던 것들이다. Reddit이나 StackOverflow등을 보면 Chef를 다들 탑으로 선택하던데, 써보지 못해서 잘 모르겠다.

개요

  • Swagger를 적용한 node.js 웹사이트 하나를 띄워달라는 요청을 받음.

과정

  • 특이하게 사내 템플릿으로 적용하는 PostgreSQL이 아니라 MySQL을 쓴다고 한다....덕분에 템플릿이 없어 DB로부터 각종 값을 받아오는 api는 동작이 불가능한 상태(artifacthub가 아니라, private helm core를 사용하고 있다).
  • 코드를 받아서 돌렸더니 38 line에서"address is already use" 에러가 나온다. 다시 봐보니 개발자가 확인용 console.log를 두개 찍어놨는데, 동일한 포트로 띄워놨던 것이다...
  • 해당 부분을 수정해 다시 올렸더니 이번엔 "cannot get /"가 뜬다.
  • 검색해보니 해당 라우트 경로가 없어서 생기는 에러라고 한다.
  • livenessProbe/readinessProbe가 "/" 경로로 GET을 날리도록 설정되어있는데, 정작 어플리케이션의 "/" 경로에선 POST를 받도록 만들어놨다.... 찾아봐도 DB 연동 없이 단순 GET을 날릴 수 있도록 만든 라우터는 없었다.
    • 이야기를 들어보니, 기기측에서 루트 경로로 POST를 날리면 바로 기기 등록과 함께 api가 실행되도록 하려고 했다고 한다.
  • 따라서, "/" 경로에 새로 "GET"을 받을 수 있는 테스트 라우터를 생성해놨다.
  • app.get('/', function (req, res) { res.send('테스트입니다.'); });
  • 빌드 성공!

환경 구축

image 용도 port-forwarding
nginx:1.21.1 reverse proxy, LB 18080 -> 8080
alpine:3.13 server1, server2 X

동작 예시

/s1으로 요청할 경우
/s2로 요청할 경우
/로 요청할 경우

구조

디렉토리 구조.

docker-compose.yml

version: "3"
services:
  nginx:
    image: nginx:1.21.1
    container_name: nginx
    hostname: nginx
    networks:
      nginx-net:
        ipv4_address: 172.11.0.10
    volumes:
    - ./nginx/nginx_conf/:/etc/nginx/conf.d/
    - ./logs/:/logs/
    ports:
    - 18080:8080
  server1:
    build: ./server1
    container_name: server1
    hostname: server1
    networks:
      nginx-net:
        ipv4_address: 172.11.0.11
  server2:
    build: ./server2
    container_name: server2
    hostname: server2
    networks:
      nginx-net:
        ipv4_address: 172.11.0.12
networks:
  nginx-net:
    ipam:
      driver: default
      config:
      - subnet: 172.11.0.0/26
volumes:
  nginx:

nginx config

upstream backend {
        # backed로 선언된 주소의 경우 하기 두 서버가 요청을 나눠가진다
        # weights=INTEGER 변수로 가중치를 주어 해당 서버가 더 많은 요청을 가져가게 할 수도 있다.
        # 타겟 서버간 스펙이 다를 경우 수동 부하분담을 위해 설정
        server 172.11.0.11:8080;
        server 172.11.0.12:8080;
}

server {
        # 8080 포트로 들어오는 요청에 대해 응답
        listen 8080;
        listen [::]:8080;
        server_name localhost;

        # 접속 로그 및 오류 로그
        access_log /logs/access.log;
        error_log /logs/error.log;

        # LB with no weights
        location / {
                proxy_pass http://backend;
        }

        # 해당 경로로 들어오는 요청에 대해 reverse proxy 동작
        location /s1 {
                proxy_pass http://172.11.0.11:8080;
        }
        location /s2 {
                proxy_pass http://172.11.0.12:8080;
        }
}

- nginx.conf의 경우 일반적인 HTTP 요청으로 들어올 것으로 가정해 따로 손대지 않았다.

Server1

from flask import Flask

app=Flask(__name__)

@app.route('/')
def home():
    return "this is server1 home"

@app.route('/s1')
def s1():
    return "this is server1 home to /s1"

app.run(host="0.0.0.0", port=8080)

Server2

from flask import Flask

app=Flask(__name__)

@app.route('/')
def home():
    return "this is server2 home"

@app.route('/s2')
def s2():
    return "this is server2 home to /s2"

app.run(host="0.0.0.0", port=8080)

Dockerfile(server1, server2 동일)

FROM alpine:3.13

COPY server.py /server.py

RUN apk update && apk add python3 py3-pip \
        && python3 -m pip install --upgrade pip \
        && python3 -m pip install flask

CMD python3 server.py

결과

example

https://github.com/Jangab-water/nginx-training

+ Recent posts