데이터 엔지니어( 실습 정리 )

도커 컨테이너 실습[주키퍼] 동적 실행

세용용용용 2024. 7. 3. 10:12

업데이트(Update)

2024-07-01
- 주키퍼 개념 작성

2024-07-02
- 도커 레지스트리 활용 컨테이너 실행으로 스크립트 변경
- proc.sh 스크립트 수정(절차지향 >>> 객체지향) 재 사용성을 위해 수정하였습니다.
- component_proc.py(system_download.txt 배포부터 ~ 컨테이너run 자동화 클래스 구현)

2024-07-03
- 엔트리포인트 스크립트(주키퍼 설정 setup 스크립트) 수정
- zoo.cfg 파일 datadir 등 여러 옵션 동적 추가

 

- zookeeper : 분산시스템 코디네이션 서비스로 개발

1) 분산 코디네이션

- 분산 시스템에서 시스템 간의 정보 공유, 상태 체크, 서버들 간의 동기화를 위한 락 등을 처리해주는 서비스

- 즉, 주키퍼는 분산 시스템의 서비스 이기에 동작을 멈추면 분산 시스템도 멈출수 있다... 안정성 확보를 위해 클러스터 구축 (고가용성 필수)

 

2) zookeeper 구조

Client : 분산 애플리케이션

주키퍼 서버를 클러스터 구축후 각각의 클라이언트와 커넥션 완료후, 상태 정보를 주고 받음

- 상태 정보는 zookeeper의 znode에 키-벨류 형태로 저장(인 메모리, 디스크 둘다 기록)

Request Processor
- 쓰기 요청(모든 쓰기 요청을 리더에게)

Atomic Broadcast
- zab(주키퍼 atomic broadcast 프로토콜) 사용
- 리더는 팔로워 에게 업데이트를 broadcast 한다.
- 과반수 노드에서 변경을 저장시 리더는 업데이터 연산을 commit하고 
- 클라이언트는 업데이터 성공했다는 응답을 받는다!!! 
- 변경 결과는 성공, 실패 둘중하나!!!

in-memoryDB 
- 주키퍼는 상태 정보를 인메모리 저장소에 저장( 빠른 검색 )
- 업데이트 사항을 znode 트리 메모리 복사본에 기록하기전 디스크에도 기록하며 일관성 유지
- znode : 상태 정보를 key-value의 형태로 저장
  znode 에 저장된 것을 이용해 분산 애플리케이션들은 데이터를 주고받음

 

 

client(kafka) 데이터 쓰기 과정

1. 특정 서버에 접속하여 서버 데이터 업데이트

2. 해당 서버는 Leader 서버에 데이터가 업데이트 되었음을 전송

3. Leader 서버는 업데이트 신호를 받고, 다른 Follower 서버들에게 브로드캐스트(Broadcast) 형식으로 전송

4. 나머지 Follower 서버들도 데이터 업데이트

5. 과반수 이상 변경 저장시 리더는 연산을 commit

6. 분산 애플리케이션은 연산을 성공했다는 응답을 받음( 여기서 결과는 성공 아니면 실패 무조건 둘 중 하나!!!)

 

0. system_download.txt

[server_ip]|192.168.56.10|192.168.56.11|192.168.56.12
[zookeeper_ip]|192.168.56.10|192.168.56.11|192.168.56.12
[postgresql_ip]|192.168.56.10
[postgresql_port]|5432

-----------------------[zoo.cfg-start]-----------------------
[tickTime=]|2000
[initLimit=]|11
[syncLimit=]|5
[dataDir=]|/data/sy0218/apache-zookeeper-3.7.2-bin/data
[clientPort=]|2181
-----------------------[zoo.cfg-end]-----------------------

 

1. entrypoint.sh : 컨테이너 실행시 동작 셋업

/data/zookeeper_docker/entrypoint.sh

#!/bin/bash

system_file="/data/system_download.txt"

zoo_server="/data/system_download.txt"
zoo_cfg="/data/sy0218/apache-zookeeper-3.7.2-bin/conf/zoo.cfg"

zoo_array=($(cat ${zoo_server} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

sed -i '/server.*:2888:3888/d' ${zoo_cfg}

for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo "server.$((i+1))=${current_ip}:2888:3888" >> ${zoo_cfg}
done

echo "${ZOO_MY_ID}" > /data/sy0218/apache-zookeeper-3.7.2-bin/data/myid


zoo_config=$(awk '/\[zoo.cfg-start\]/{flag=1; next} /\[zoo.cfg-end\]/{flag=0} flag' ${system_file})
while IFS= read -r zoo_config_low;
do
        file_name=$(find /data/sy0218/*zookeeper*/conf -type f -name *zoo.cfg*)
        zoo_env_name=$(echo $zoo_config_low | awk -F '|' '{print $1}' | sed 's/[][]//g')
        zoo_env_value=$(echo $zoo_config_low | awk -F '|' '{print $2}')
        sed -i "s|^${zoo_env_name}.*$|${zoo_env_name}${zoo_env_value}|" ${file_name}
done <<< $zoo_config


/data/sy0218/apache-zookeeper-3.7.2-bin/bin/zkServer.sh start-foreground

 

2. dockerfile

/data/zookeeper_docker/dockerfile

FROM openjdk:8-jre-alpine

# 작업 디렉토리 설정
WORKDIR /data

# bash 추가로 설치 해야됨
RUN apk add --no-cache bash
# 주키퍼 설치 및 설정 파일 복사
RUN mkdir -p /data/sy0218
RUN mkdir -p /data/download_tar
# 주키퍼 tar파일 download_tar 디렉토리 복사
COPY apache-zookeeper-3.7.2-bin.tar.gz /data/download_tar/apache-zookeeper-3.7.2-bin.tar.gz
# 주키퍼 tar파일 원하는 경로에 풀기
RUN tar xzvf /data/download_tar/apache-zookeeper-3.7.2-bin.tar.gz -C /data/sy0218/
# 주키퍼 설정 파일 작업 디렉토리로 복사
COPY zoo.cfg /data/sy0218/apache-zookeeper-3.7.2-bin/conf/zoo.cfg
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# id 값 설정
RUN mkdir -p /data/sy0218/apache-zookeeper-3.7.2-bin/data

# 엔트리포인트 설정
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

 
 

3. docker run

1) 예시
docker run -d --name zookeeper \
--network host \
-v /data/work/system_download.txt:/data/system_download.txt \
-e ZOO_MY_ID=1 \
zookeeper:3.7.2

2) zookeeper_id 동적 생성
docker run -d --name zookeeper \
--network host \
-v /data/work/system_download.txt:/data/system_download.txt \
-e ZOO_MY_ID=$((i+1)) \
zookeeper:3.7.2

 

4. zoo.cfg : 주키퍼 설정파일 샘플

/data/zookeeper_docker/zoo.cfg

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/data/sy0218/apache-zookeeper-3.7.2-bin/data
clientPort=2181

 

dataDir : 스냅샷 파일이나, 트랜잭션 로그 와 같은 주키퍼 핵심 파일들이 저장
- 스냅샷 파일 : 정기적으로 데이터 스냅샷을 생성하여 현재 데이터 상태를 저장

- 트랜잭션 로그 : 모든 업데이트를 트랜잭션 로그에 기록, 주키퍼 서버가 실행 중에 데이터의 변경 사항을 기록

 

5. system_download.txt >> 컨테이너 동작시 참조하는 파일 전 서버 배포

/data/work/scp_system_download_txt.sh

#!/usr/bin/bash

ip_file="/data/work/system_download.txt"
ip_array=($(cat ${ip_file} | grep server_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_array=${#ip_array[@]}

# 현재 ip와 다음ip 출력
for ((i=1; i<len_array; i++));
do
        current_ip=${ip_array[$i]}
        echo "${current_ip}서버로 파일전송"
        scp ${ip_file} ${current_ip}:/data/work/
done

 
 

6. 주키퍼 서버만 docker 빌드 디렉토리 배포, docker 빌드

6-1. scp_zookeeper_docker_dir.sh : docker 빌드 디렉토리 배포

/data/docker_scp_sh/scp_zookeeper_docker_dir.sh

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 1 ]; then
        echo "Usage: $0 <docker_build_dir>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

docker_build_dir=$1
for ((i=1; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo $current_ip
        scp -r ${docker_build_dir} ${current_ip}:/data/
done

 
 

6-2. zookeeper_docker_build.sh : docker 빌드

/data/docker_build_sh/zookeeper_docker_build.sh

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 3 ]; then
        echo "Usage: $0 <docker_build_dir> <docker_images_name> <tag>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

docker_build_dir=$1
docker_images_name=$2
docker_images_tag=$3
for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        ssh ${current_ip} "docker build -t ${docker_images_name}:${docker_images_tag} ${docker_build_dir}"
done

 
컨테이너 빌드 확인

 

7. zookeeper_docker_run.sh >> 주키퍼 컨테이너 동적 run스크립트

/data/docker_run_sh/zookeeper_docker_run.sh

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 3 ]; then
        echo "Usage: $0 <docker_run_name> <docker_images_name> <tag>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

docker_run_name=$1
docker_images_name=$2
docker_images_tag=$3

for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo "주키퍼 시작서버:$current_ip 주키퍼 id:$((i+1))"
        ssh ${current_ip} "docker run -d --name ${docker_run_name} --network host -v /data/work/system_download.txt:/data/system_download.txt -e ZOO_MY_ID=$((i+1)) ${docker_images_name}:${docker_images_tag}"
done

 
컨테이너 동작 확인

 

8. 주피커 잘 동작하는지 확인(zookeeper_check.sh)

/data/check/zookeeper_check.sh

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 2 ]; then
        echo "Usage: $0 <container_name> <zookeeper_version>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}


container_name=$1
zookeeper_version=$2

for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo "------------zookeeper type ${current_ip}------------"
        ssh -t ${current_ip} "docker exec -it ${container_name} /data/sy0218/apache-zookeeper-${zookeeper_version}-bin/bin/zkServer.sh status"
        echo "---------------------------------------------------"; echo"";
done

 

스크립트 실행시 잘동작하는것 확인 192.168.56.11 서버가 리더로 선출됨!!!
 

9. 주피커 자동화 스크립트 (/data/work/component_proc_sh/zookeeper_proc.sh)

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 4 ]; then
    echo "Usage: $0 <docker_build_dir> <docker_images_name> <tag> <docker_run_name>"
    exit 1
fi

docker_build_dir=$1
docker_images_name=$2
docker_images_tag=$3
docker_run_name=$4

echo "[`date`] Time_Stamp : zookeeper docker auto Start...."

echo "[`date`] Time_Stamp : 각 서버 최신화 system_download.txt 배포 Start...."
/data/work/scp_system_download_txt.sh
echo "[`date`] Time_Stamp : 각 서버 최신화 system_download.txt End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker build_dir scp Start...."
/data/docker_scp_sh/scp_zookeeper_docker_dir.sh ${docker_build_dir}
echo "[`date`] Time_Stamp : zookeeper docker build_dir scp End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker build Start...."
/data/docker_build_sh/zookeeper_docker_build.sh ${docker_build_dir} ${docker_images_name} ${docker_images_tag}
echo "[`date`] Time_Stamp : zookeeper docker build End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker run Start...."
/data/docker_run_sh/zookeeper_docker_run.sh ${docker_run_name} ${docker_images_name} ${docker_images_tag}
echo "[`date`] Time_Stamp : zookeeper docker run End...."; echo "";
echo "[`date`] Time_Stamp : zookeeper docker auto End...."

 

10. 도커 레지스트리를 활용한 컨테이너 실행

하둡 클러스터 운영( 도커 컨테이너 ) 실습[주키퍼] (tistory.com)

 

하둡 클러스터 운영( 도커 컨테이너 ) 실습[주키퍼]

1. 도커 설치( ubuntu )1. 패키지 업데이트sudo apt update2. 필요한 패키지 설치# apt-transport-https : HTTPS 프로토콜을 사용하는 외부 저장소에서 패키지를 다운로드하고 설치하는 데 필요# ca-certificates : HTTP

sy02229.tistory.com

도커 레지스트리 구축은 해당 글에 포함되있습니당!!

 

도커 레지스트리 컨테이너를 가동

 

10-0. zookeeper가 추가된 system_download.txt 각 서버 배포(/data/work/scp_system_download_txt.sh)

#!/usr/bin/bash

ip_file="/data/work/system_download.txt"
ip_array=($(cat ${ip_file} | grep server_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_array=${#ip_array[@]}

# 현재 ip와 다음ip 출력
for ((i=1; i<len_array; i++));
do
        current_ip=${ip_array[$i]}
        echo "${current_ip}서버로 파일전송"
        scp ${ip_file} ${current_ip}:/data/work/
done

 

10-1. 이미지 빌드

<레지스트리 주소>/<이미지 이름>:<태그>
docker build -t 192.168.56.10:5000/zookeeper:3.7.2 .
레지스트리 주소 : 192.168.56.10:5000
이미지 이름 : zookeeper
태그 : 3.7.2

 

 

 

10-2. 이미지 푸쉬 (이미지를 레지스트리로 푸쉬)

# 레지스트리 푸쉬
docker push 192.168.56.10:5000/zookeeper:3.7.2

# 도커 레지스트리에 이미지 푸쉬 확인
curl -X GET http://192.168.56.10:5000/v2/zookeeper/tags/list

 

 

10-3. 주키퍼 띄울 각 서버 도커 레지스트리에 있는 이미지 가져오기(pull)

docker pull 192.168.56.10:5000/zookeeper:3.7.2

/data/docker_sh/zookeeper_docker_pull.sh

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 3 ]; then
        echo "Usage: $0 <docker_registry> <docker_images_name> <tag>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

docker_registry=$1
docker_images_name=$2
docker_images_tag=$3

for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo "주키퍼 이미지 레지스트리 >>> pull >>> 서버:$current_ip"
        ssh ${current_ip} "docker pull ${docker_registry}/${docker_images_name}:${docker_images_tag}"
done

 

 

 

10-4. 주키퍼 컨테이너 시작(/data/docker_sh/zookeeper_docker_run.sh)

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 4 ]; then
        echo "Usage: $0 <docker_registry> <docker_images_name> <tag> <docker_run_name>"
        exit 1
fi

zoo_server_file="/data/work/system_download.txt"
zoo_array=($(cat ${zoo_server_file} | grep zookeeper_ip | awk -F '|' '{for(i=2; i<=NF; i++) print $i}'))
len_zoo_array=${#zoo_array[@]}

docker_registry=$1
docker_images_name=$2
docker_images_tag=$3
docker_run_name=$4

for ((i=0; i<len_zoo_array; i++));
do
        current_ip=${zoo_array[$i]}
        echo "주키퍼 시작서버:$current_ip 주키퍼 id:$((i+1))"
        ssh ${current_ip} "docker run -d --name ${docker_run_name} --network host -v /data/work/system_download.txt:/data/system_download.txt -e ZOO_MY_ID=$((i+1)) ${docker_registry}/${docker_images_name}:${docker_images_tag}"
done

 

10-5. 주키퍼 동작 확인(/data/check/zookeeper_check.sh)

 

10-6. 최종 스크립트(/data/work/component_proc_sh/zookeeper_proc.sh)  - 절차지향 프로그래밍

#!/usr/bin/bash

# 인자 개수 확인
if [ "$#" -ne 5 ]; then
    echo "Usage: $0 <docker_build_dir> <docker_registry> <docker_images_name> <tag> <docker_run_name>"    exit 1
fi

docker_build_dir=$1
docker_registry=$2
docker_images_name=$3
docker_images_tag=$4
docker_run_name=$5

echo "[`date`] Time_Stamp : zookeeper docker auto Start...."

echo "[`date`] Time_Stamp : 각 서버 최신화 system_download.txt 배포 Start...."
/data/work/scp_system_download_txt.sh
echo "[`date`] Time_Stamp : 각 서버 최신화 system_download.txt End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker build Start...."
docker build -t "${docker_registry}/${docker_images_name}:${docker_images_tag}" "${docker_build_dir}"
echo "[`date`] Time_Stamp : zookeeper docker build End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker registry Push Start...."
docker push "${docker_registry}/${docker_images_name}:${docker_images_tag}"
echo "[`date`] Time_Stamp : zookeeper docker registry Push End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker registry Check Start...."
curl -X GET http://${docker_registry}/v2/zookeeper/tags/list
echo "[`date`] Time_Stamp : zookeeper docker registry Check End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker Pull Start...."
/data/zookeeper/zookeeper_docker_pull.sh ${docker_registry} ${docker_images_name} ${docker_images_tag}echo "[`date`] Time_Stamp : zookeeper docker Pull End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker Run Start...."
/data/zookeeper/zookeeper_docker_run.sh ${docker_registry} ${docker_images_name} ${docker_images_tag} ${docker_run_name}
echo "[`date`] Time_Stamp : zookeeper docker Run End...."; echo "";

echo "[`date`] Time_Stamp : zookeeper docker auto End...."

 

 

10-7. 리팩토링 (/data/work/component_proc_sh/component_proc.py)  - 객체지향 프로그래밍

#!/usr/bin/python3

import subprocess
import sys
from datetime import datetime

# DockerManager 클래스 정의
class DockerManager:
    # 생성자 메서드 : 클래스의 인스턴스가 생성될 때 자동으로 호출, 클래스의 인스턴스 변수들을 초기화!!
    # self : 인스턴스 메서드를 호출하거나 변수에 접근하는 특별한 매개변수
    def __init__(self, docker_build_dir, docker_registry, docker_images_name, docker_images_tag, docker_run_name):
        self.docker_build_dir = docker_build_dir
        self.docker_registry = docker_registry
        self.docker_images_name = docker_images_name
        self.docker_images_tag = docker_images_tag
        self.docker_run_name = docker_run_name

    def log(self, message):
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Time_Stamp : {message}")

    def system_download(self):
        self.log("각 서버 최신화 system_download.txt 배포 Start....")
        subprocess.run(['/data/work/scp_system_download_txt.sh'], check=True)
        self.log("각 서버 최신화 system_download.txt 배포 End....")
        print("")

    def docker_build(self):
        self.log("docker build Start....")
        subprocess.run([
            'docker', 'build', '-t',
            f"{self.docker_registry}/{self.docker_images_name}:{self.docker_images_tag}",
            self.docker_build_dir], check=True)
        self.log("docker build End....")
        print("")

    def docker_push(self):
        self.log("docker registry Push Start....")
        subprocess.run([
            'docker', 'push',
            f"{self.docker_registry}/{self.docker_images_name}:{self.docker_images_tag}"], check=True)
        self.log("docker registry Push End....")
        print("")

    def docker_registry_check(self):
        self.log("docker registry Check Start....")
        subprocess.run([
            'curl', '-X', 'GET',
            f"http://{self.docker_registry}/v2/zookeeper/tags/list"], check=True)
        self.log("docker registry Check End....")
        print("")

    def docker_pull(self):
        self.log("docker Pull Start")
        if self.docker_run_name == 'zookeeper':
            subprocess.run([
                '/data/docker_sh/zookeeper_docker_pull.sh',
                self.docker_registry,
                self.docker_images_name,
                self.docker_images_tag], check=True)
        elif self.docker_run_name == 'postsql':
            subprocess.run([
                '/data/docker_sh/postgresql_docker_pull.sh',
                self.docker_registry,
                self.docker_images_name,
                self.docker_images_tag], check=True)
        self.log("docker Pull End")
        print("")

    def docker_run(self):
        self.log("docker Run Start....")
        if self.docker_run_name == 'zookeeper':
            subprocess.run([
                '/data/docker_sh/zookeeper_docker_run.sh',
                self.docker_registry,
                self.docker_images_name,
                self.docker_images_tag,
                self.docker_run_name], check=True)
        if self.docker_run_name == 'postsql':
            subprocess.run([
                '/data/docker_sh/postgresql_docker_run.sh',
                self.docker_registry,
                self.docker_images_name,
                self.docker_images_tag,
                self.docker_run_name], check=True)
        self.log("docker Run End....")
        print("")

    def run(self):
        self.log("docker auto Start....")
        self.system_download()
        self.docker_build()
        self.docker_push()
        self.docker_registry_check()
        self.docker_pull()
        self.docker_run()
        self.log("docker auto End....")


if __name__ == "__main__":
    args = sys.argv[1:]
    # 인자가 5개가 아니면 종료
    if len(args) != 5:
        print("사용법: script.py <docker_build_dir> <docker_registry> <docker_images_name> <tag> <docker_run_name>")
        sys.exit(1)
    # 각 인자를 변수로 저장
    docker_build_dir, docker_registry, docker_images_name, docker_images_tag, docker_run_name = args

    # 컨테이너 run 파이프라인 실행
    manager = DockerManager(docker_build_dir, docker_registry, docker_images_name, docker_images_tag, docker_run_name)
    manager.run()

 

proc.sh 실행

./component_proc.py /data/zookeeper_docker 192.168.56.10:5000 zookeeper 3.7.2 zookeeper