YOGYUI

홈네트워크 파이썬 앱 Multi-platform Docker Image 만들기 본문

홈네트워크(IoT)/일반

홈네트워크 파이썬 앱 Multi-platform Docker Image 만들기

요겨 2024. 2. 21. 14:26
반응형

 

 

Create Multi-platform Docker Images for Home Network Python Application

내가 만든 힐스테이트 광교산용 RS-485 연동 홈네트워크 파이썬 코드를 Home Assistant OS(HAOS)에서 구동하는 방법을 포스팅한 뒤, 이에 관해 사용법이나 문제 보고하는 유저들이 조금씩 늘어나고 있다

HAOS에서 현대통신 RS485 연동 GitHub python 코드 실행하기

 

HAOS에서 현대통신 RS485 연동 GitHub python 코드 실행하기

2023년 막바지에 HAOS에서 내가 깃허브(GitHub)에 올려둔 힐스테이트 광교산 소스코드(현대통신 HDHN-2000 월패드 RS485 연동)를 HAOS가 설치된 SBC/NUC에서 실행하는 방법에 대한 문의가 3건 가량 들어왔다

yogyui.tistory.com

 

올해(2024년) 2월 초에 유저 한분의 HA 세팅을 도와드린 뒤 받은 카톡메시지가 심금을 울린다 (???)

 

담다패드가 뭔진 모르겠지만 아무튼 HA에서 사용할 수 있는 애드온을 제작하면 많은 사람들이 더 편하게 사용할 수 있을 것 같다는 의견...

 

이때까지의 원격 지원 경험상 내가 만든 코드는 오로지 힐스테이트 광교산에서만 완벽하게 동작하지, 다른 아파트에서는 패킷 구조가 상이하거나 등의 이유로 불완전하게 동작하기 때문에 General하게 사용할 수 있는 애드온을 만드는 건 시기상조라는 게 현재의 내 생각...

 

그래도 HA 애드온을 만드려면 Docker 컨테이너를 활용해야 되기 때문에 이 기회에 Docker 컨테이너용 이미지를 만드는 법을 학습해보기로 했다

도커는 남이 만들어둔 이미지를 가져가 쓰는 것만 익숙하지, 직접 만들어보는 건 이번이 처음... ^^;;

따라서 이 포스팅은 거듭된 삽질 끝에 완성된 개발 일지(라기보다는 summary)라 할 수 있겠다 ㅋㅋ


HA는 여러 플랫폼(라즈베리파이, NUC, NAS, 랩탑 등)에서 구동할 수 있는데, CPU 아키텍쳐가 제각각 다르기 때문에 각각의 아키텍쳐에 맞게 도커 이미지를 따로 빌드해줘야 한다

Docker 이미지 빌드에 사용한 호스트 PC(랩탑)의 사양은 다음과 같다

[Host PC] - Dell Precision 5520
CPU: Intel(R) Core(TM) i7-6820HQ @ 2.70GHz (Quad Core)
RAM: DDR4 16GB
OS: Ubuntu 22.04.3 LTS (linux kernel: 6.5.0-15-generic)

1. 도커 (Docker) 설치 및 설정

이미지를 빌드할 호스트 PC에 도커를 설치해준다

참조: docker 공식 가이드 페이지

1.1. 도커 엔진 설치

APT(Advanced Packaging Tool)를 통해 도커 엔진 및 유틸리티들을 설치해준다

※ 공식 가이드 페이지에서 get-docker.sh로 편하게 설치하는 방법도 있으니 참고

 

1.1.1. GPG Key 추가 및 APT 리포지터리 추가

$ sudo apt-get install ca-certificates curl
$ sudo install -m 0755 -d /etc/apt/keyrings
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
$ sudo chmod a+r /etc/apt/keyrings/docker.asc
$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update

 

1.1.2. 패키지 설치

릴리즈된 최신 버전은 다음 명령어로 설치할 수 있다

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

만약 특정 버전을 설치하고자 할 경우, docker-ce와 docker-ce-cli 패키지 뒤에 버전명을 명시하면 된다

(예시)

$ VERSION_STRING=5:24.0.0-1~ubuntu.22.04~jammy
$ sudo apt-get install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin docker-compose-plugin

 

apt 리포지터리에 등록된 도커 버전 리스트는 다음 명령어로 확인할 수 있다

$ apt-cache madison docker-ce | awk '{ print $3 }'

 

1.1.3. 버전 및 동작 확인

설치된 도커 엔진의 버전은 다음 명령어로 확인할 수 있다

$ docker --version
$ docker -v

2024년 2월 21일 기준 최신 버전은 25.0.3

 

제대로 동작하는지 확인해보자

  • hello-world 이미지를 풀(pull)해와 실행
$ sudo docker run hello-world

 

제대로 동작하는지 확인했으면 이미지를 지워주자

$ sudo docker rmi hello-world:latest --force

 

1.1.4. 도커 제거 방법 (옵션)

위 과정을 통해 설치된 apt 패키지들을 제거하고자 할 경우 다음과 같이 apt 명령어를 호출하면 된다

$ sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

 

도커 개발 과정에서 로컬 PC에 생성된 도커 이미지, 볼륨, 컨테이너 및 설정 파일은 별도로 삭제해줘야 한다

$ sudo rm -rf /var/lib/docker
$ sudo rm -rf /var/lib/containerd

1.2. 도커 개발 환경 설정

1.2.1. 비루트 계정(Non-root user) 권한 등록

도커 데몬은 Unix 소켓을 바인딩하는데, Unix 소켓의 소유권은 루트(root) 계정이기 때문에 쉘에서 접근하기 위해서는 항상 슈퍼유저 권한으로 실행하기 위해 sudo 명령어를 맨 앞에 붙여줘야 한다

도커 관련 작업을 하다보면 일일이 sudo를 붙이는 게 여간 번거로운 게 아니기 때문에 비루트 계정에서 sudo 없이 사용할 수 있게 만들어주는 게 편하다 (아니면 su 혹은 sudo -s로 루트 계정으로 진입해 작업해도 된다)

공식가이드 참고 (https://docs.docker.com/engine/install/linux-postinstall/)

 

사용자그룹 생성 (이름은 아무렇게나 해도 되지만, 가이드에 따라 docker로 설정)

$ sudo groupadd docker

 

현재 로그인된 (비루트)사용자그룹에 유저 추가

$ sudo usermod -aG docker $USER

 

사용자그룹 활성화

$ newgrp docker

 

1.2.2. 시스템 시작 시 도커 자동실행 설정

데비안/우분투 OS는 도커 설치 후 자동으로 부팅 시 시작 기능이 활성화되지만, 다른 리눅스 OS의 경우 systemctl 명령어로 기능을 활성화해줘야 한다 (내 환경은 우분투이기 때문에 Pass~)

$ sudo systemctl enable docker.service
$ sudo systemctl enable containerd.service

2. 도커 허브 (Docker Hub) 리포지터리 생성

내가 만든 멀티플랫폼 이미지를 손쉽게 업로드, 다운로드(push, pull)하기 위해 도커 허브 저장소를 사용하도록 한다 

도커 허브는 무료 계정으로도 공개 리포지터리는 무한으로 생성할 수 있고, 쿠버네티스가 지원되며 6시간마다 최대 200개 이미지 풀(pull)이 가능하기 때문에 개인 차원에서 소소하게 이미지를 배포하기 위해 사용하기 적합하다 (다만 무료 계정은 비공개=private 리포지터리는 최대 1개까지만 생성 가능)

 

유료 계정 중 하나인 Pro도 월 $5이라 하드코어하게 많이 배포하는 개인 개발자가 도입하기에도 크게 부담없는 금액이라는 점이 특징! 비공개 리포지터리를 무한정 생성할 수 있는건 개발자 입장에서 군침흘릴만한 조건인 것 같다

(Team이나 Business 유료 계정은 말 그대로 개발팀이나 기업 차원에서 도입을 고려하는 계정)

 

홈네트워크 어플리케이션 만들어봐야 가져다 사용하는 유저는 극소수일테니 딱히 유료 계정을 도입할 생각은 없다 (재직중인 회사는 Team 계정을 도입하긴 했는데, 나는 쿠버네티스 개발자가 아니라 정작 사용해본 적이 없다 ㅋㅋ)

2.1. 회원가입

도커 허브 홈페이지에서 회원 가입을 한다 (https://hub.docker.com/

이미 회원가입된 상태라면 PASS

 

Docker Hub Container Image Library | App Containerization

Increase your reach and adoption on Docker Hub With a Docker Verified Publisher subscription, you'll increase trust, boost discoverability, get exclusive data insights, and much more.

hub.docker.com

메인페이지에서 'Sign Up' 클릭 (혹은 Get Started)

구글이나 깃허브(GitHub) 계정으로 가입하거나 혹은 이메일로 가입할 수 있다

※ 나는 구글 계정으로 가입했는데, 다른 이메일로 가입하면 confirm 메일을 확인하는 절차를 추가로 거친다고 한다

 

2.2. 액세스 토큰(Access Token) 발급

도커 허브 페이지 우측 상단의 프로파일 버튼 클릭 후 'My Account' 버튼 클릭

 

좌측 메뉴에서 'Security' 클릭

 

Access Tokens 탭의 'New Access Token' 클릭

 

Access Token Description에 토큰 설명 (아무렇게나 적어도 됨)을 기입하고, Access permissions는 'Read, Write, Delete' 선택 후 'Generator' 버튼 클릭

 

아래와 같이 'Copy Access Token' 팝업창이 뜨는데, 회색 창 내부에 기입된 토큰은 이 창이 닫히면 더이상 확인할 수 없기 때문에 노트패드같은데 복사-붙여넣기 해두자

  • 'Copy and Close' 버튼을 클릭하면 자동으로 클립보드에 토큰이 복사되며 창이 닫힌다
  • 만약 토큰 값을 까먹었다면 새로 발급받으면 된다 ㅎㅎ

 

액세스 토큰을 생성하면 회원가입 시 사용한 메일 주소로 알림 메일이 발송된다

2.3. Docker Hub 로그인

터미널에서 도커 허브에 로그인해준다

$ docker login -u {도커허브 계정명}

 

로그인 명령을 입력하면 Password를 입력하라는 문구가 나오는데, 앞서 2.2에서 발급받은 액세스 토큰을 붙여넣으면 된다

정상적으로 로그인되면 'Login Succeeded' 문구를 볼 수 있다

로그를 보면 ~/.docker/config.json 파일에 계정 관련 정보가 저장되어 있다고 한다

파일 내부를 확인해보면

https://index.docker.io/v1/ 주소에 대한 계정 정보가 저장된 것을 확인할 수 있으며, 이를 활용해 앞으로 별도의 login 절차없이 도커 허브 리포지터리에 접근할 수 있다

2.4. 리포지터리(Repository) 생성

도커 허브에 로그인한 뒤 'Repositories' 탭 진입 후 'Create repository' 버튼 클릭

 

Namespace가 계정명으로 고정되며, Repository Name에 고유한 저장소 이름을 기입하면 된다

Short description에 저장소에 대한 설명을 간략히 기재한 후 'Create' 버튼 클릭

※ 앞서 언급했듯이 무료 계정(Personal)은 최대 1개의 private repo만 생성 가능하

 

생성이 완료되면 아래와 같은 화면을 볼 수 있다

3. Docker BuildX 멀티플랫폼 빌드 환경 구축

amd64, arm/v7, arm64 등 서로 다른 CPU 아키텍쳐 환경에서 내가 만든 어플리케이션이 모두 동작하게 하려면 아키텍쳐별로 이미지를 따로 빌드하여 저장소에 올려야 한다

도커로 제일 손쉽게 멀티플랫폼 이미지를 생성하는 방법은 QEMU 하이퍼바이저(혹은 버추얼라이저)를 활용하는 것인데, 어플리케이션이 고성능을 요구하지 않는다면 도커 설치 시 함께 제공되는 BuildX를 사용해 명령어 한줄로 멀티플랫폼 이미지 생성 및 도커 허브 푸시(push)까지 한번에 진행하면 된

※ 도커 공식 가이드(https://docs.docker.com/build/building/multi-platform/)에 따르면, QEMU를 활용한 에뮬레이션은 네이티브 빌드에 비해 성능이 떨어지기 때문에 크로스컴파일 등 다른 방법을 권장하고 있다

3.1. 추가 플랫폼을 위한 binfmt 도커 이미지 설치

Ubuntu 초기 설치 시 함께 설치된 binfmt_misc에 등록된 플랫폼은 한정적이기 때문에, docker buildx가 처리할 수 있는 플랫폼을 확장하기 위해 다음 명령어로 도커 이미지를 설치해준다

 

$ docker run --privileged --rm tonistiigi/binfmt --install all

※ 리눅스 커널 4.8 이상, binfmt-support 버전 2.1.7 이상에서만 지원

 

arm/v6, arm/v7, arm64, amd64 등 일반적으로 쓰이는 아키텍쳐 외에 RISC-V같은 최신 아키텍쳐에서도 빌드할 수 있는 환경을 제공한다

 

buildx에 설정되어 있는 빌더(builder)를 리스트업 해보자

$ docker buildx ls

 

앞서 binfmt 이미지에서 추가된 아키텍쳐들에 대한 빌드가 지원되는 것을 알 수 있다

3.2. 새로운 빌더 생성

docker container build 드라이버(링크)를 기반으로 한 새로운 빌더를 생성해보자

$ docker buildx create --name mybuilder --driver=docker-container
$ docker buildx use mybuilder
$ docker buildx inspect --bootstrap

 

이름은 임의로 mybuilder라고 명명했는데, 아무렇게나 지어도 상관없다

빌더 생성 시 드라이버를 docker-container로 명시했으며, 생성 - 활성화 - 설치 세 단계로 나누어 터미널에 명령어를 입력하면 다음과 같은 화면을 볼 수 있다

 

만약 명령어 한줄로 처리하고 싶다면 아래와 같이 인자를 모두 붙여주면 된다

$ docker buildx create --name mybuilder --driver=docker-container --bootstrap --use

 

빌더를 새로 리스트업해보자

$ docker buildx ls

 

현재 활성화된 빌더(mybuilder)를 제거하고 싶으면 rm 명령을 호출해주면 된다

$ docker buildx rm

4. 도커 이미지 생성 및 푸시

4.1. Dockerfile 작성

수 일의 삽질 끝에 멀티플랫폼에서 구동되는 도커 이미지를 생성하는 Dockerfile을 작성할 수 있었다 ㅋㅋ

# syntax=docker/dockerfile:1
FROM alpine:latest
LABEL maintainer="YOGYUI lee2002w@gmail.com"

# install required packages
RUN apk add --update --no-cache \
    bash curl nano vim wget git openrc \
    gcc openssl-dev libffi-dev musl-dev linux-headers cargo pkgconfig \
    python3 python3-dev py3-pip

# create directory & copy source code & set working directory
RUN mkdir -p /repos/yogyui/homenetwork/hillstate-gwanggyosan
COPY . /repos/yogyui/homenetwork/hillstate-gwanggyosan
WORKDIR /repos/yogyui/homenetwork/hillstate-gwanggyosan

# create & activate python virtual environment, install python requirements
RUN /bin/bash -c "source ./bootstrap.sh"
RUN python3 clean.py

# expose default web server port (todo: dynamic expose?)
EXPOSE 7929

# activate python venv and launch application
CMD /bin/bash -c "source ./activate.sh; python3 app.py; /bin/bash"

 

pyOpenSSL같은 고급(?) 파이썬 패키지도 필요하기 때문에 단순히 python 이미지로 만들기가 힘들어 알파인 리눅스(Alpine Linux) 최신버전을 베이스로 이미지를 만들어야 했다 ㅋㅋ

  • 베이스 이미지 지정 = apline:latest (FROM)
  • 필요한 패키지들을 apk add 명령어로 설치 (RUN)
  • 소스코드 복사 및 작업경로 변경 (RUN, COPY, WORKDIR)
  • python3 venv 환경 구축 및 requirements.txt에 명시된 필수 패키지 설치 (RUN)
  • python3 venv 활성화 및 파이썬 어플리케이션 실행 (CMD)

최대한 단순하게 스크립트를 작성하고 동작을 테스트했는데, OpenSSH를 붙이는 작업도 필요할 것 같긴 하다 ^^;;

4.2. 도커 이미지 빌드 및 도커 허브 푸시

buildx로 빌드 시 아키텍쳐(플랫폼)을 다음과 같이 지정해주면 된다 (테스트삼아 arm32, aarch64, amd64 세 아키텍쳐를 지정)

$ docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t yogyui/homenet_hillstate --push .

 

명령은 Dockerfile 파일이 있는 디렉터리에서 호출하면 된다 (혹은 -f 인자로 파일 지정)

생성된 이미지는 캐쉬에만 남긴 채 도커 허브에 바로 푸시할 것이므로 도커 허브 저장소명을 지정해준다

※ 꽤나 성능이 좋은 호스트에서 빌드했음에도 불구하고, pyOpenSSL 파이썬 패키지 설치 시 함께 설치되는 의존성 패키지 중 하나인 cryptography가 arm/v7 아키텍쳐에서 빌드되는데 굉장히 많은 시간이 소요된다 ㅠ (이것저것 해결책을 찾아봤는데 그냥 pip upgrade하고 오랜 시간 기다리는게 제일 쉬운 것 같다 ㅋㅋ)

4.3. 도커 허브 리포지터리 확인

도커 허브 리포지터리(yogyui/homenet_hillstate)에 접속해보면 latest 태그로 이미지가 등록된 것을 볼 수 있다

amd64, arm/v7, arm64 세 아키텍쳐에서 구동 가능한 이미지들이 리포지터리에 등록되었다

 

 

5. 도커 이미지 동작 확인

이 섹션의 내용은 실제로 도커 이미지를 구동할 디바이스(라즈베리파이, NUC, NAS 등)에서 진행하면 된다

(docker가 설치되어 있어야 하는데, 위에서 이미 설명했으므로 Pass~)

5.1. 도커 이미지 풀

도커 허브 저장소로부터 이미지를 풀(pull)하는 명령어는 다음과 같다

$ docker pull yogyui/homenet_hillstate

 

 

5.2. 컨테이너 실행 데모

이미지를 컨테이너에 실어 실행하는 명령어는 다음과 같다

$ docker run -it yogyui/homenet_hillstate:latest

 

※ 포트포워딩 등 옵션은 일부러 안적었다... docker run 명령어의 옵션 플래그에 대해서는 따로 포스팅하도록 한다 (어차피 나중에는 docker-compose로 실행하게 만들어야 되긴 하지만 ~.,~)

 

서로 다른 세 아키텍쳐에서 내가 만든 이미지를 pull한 뒤 실행해보자

 

5.2.1. Ubuntu 22.04가 설치된 Intel CPU 랩탑

[1] 아키텍쳐 확인 (x68_64 = amd64)

 

[2] 이미지 풀

 

[3] 이미지 리스트업

 

[4] 이미지 실행

 

5.2.2. HAOS가 설치된 라즈베리파이

※ Advanced SSH & Web Terminal 애드온에서 제공하는 터미널에서 실행

[1] 아키텍쳐 확인 (aarch64 = arm64)

 

[2] 이미지 풀

 

[3] 이미지 리스트업

 

[4] 이미지 실행

 

5.2.3. Raspberry Pi OS가 설치된 라즈베리파이

[1] 아키텍쳐 확인 (armv7l = arm/v7)

 

[2] 이미지 풀

 

[3] 이미지 리스트업

 

[4] 이미지 실행

하... Rasbpberry Pi OS에서 실행하니 time.time() 구문에서 PermissionError 예외가 발생한다

일단 docker run 명령어 호출 시 --privileged 플래그를 붙이면 해결되긴 하는데...

$ docker run -it --privileged yogyui/homenet_hillstate:latest

이미지를 빌드할 때 해결할 수 있는지 한번 연구해봐야겠다 -_-

거의 다 됐다 싶었는데 또 걸림돌이라니...

6. TODO

내 코드에서 가장 중요한 것은 RS-485 컨버터 종류 설정, 디바이스 자동 탐색 및 HA MQTT Discovery 토픽 발행을 위해 초기에 config.xml 파일을 수정해줘야 하는 것이다 

한번만 수정하고 나면 그 뒤에는 따로 손볼 이유가 없긴 한데, 어쨌든 최소 한번은 손을 대야 하기 때문에 컨테이너 내부 로컬 파일에 접근하는 방법이 필요하다

docker exec 명령어로 컨테이너에 접속해 nano나 vim 등 에디터로 수정하는 기능은 지금도 가능하긴 한데, 비개발자 입장에선 이것도 나름 까다로운 절차일 수 있기 때문에 손쉽게 파일을 수정할 수 있는 인터페이스를 만들어야 한다

$ docker exec -it {container_id} /bin/bash

 

따라서, (어차피 HA 애드온으로 만들거라면) 다른 HA 애드온들처럼 HA 페이지 자체에서 내 어플리케이션의 설정을 변경할 수 있게 웹호스트 서비스를 좀 더 강화해야 할 것 같다 

지금도 Flask로 정말 허접한 웹서버를 구현해두긴 했는데, 이제 본격적으로 웹 프론트엔드 개발을 해야 할 것 같다 (이미 Node랑 Vue, Vuetify에 익숙해진 내가 다시 flask로 돌아가 원활한 개발을 할 수 있을지는 의문 ㅠ)


어쨌든 HA 애드온 개발 전체 과정 중 50%는 끝낸 것 같다!

남은 과정 중 25%는 웹 프론트엔드 개발, 나머지 25%는 HA Addon 테스트 정도가 될 것 같다

(어차피 HA 애드온으로서는 지금도 어찌저찌 써먹을 수는 있겠지만 ㅋㅋ)

반응형
Comments