/img/avatar.jpg

라즈베리파이 + 데비안13+에서 zymkey 설치하기

뭐냐면

별 건 아니고, zymkey 4i 드라이버 설치 스크립트가 debian 13에 적용이 안되어 있어서 제가 써먹으려고 멋대로 수정한 스크립트 입니다. install_zk_sw.sh로 저장해서 실행하세요.

스크립트

#!/bin/bash

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Copyright (C) 2021-2022 by copyright Zymbit
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without l> imitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#---------------------------------------------------------------------------------------------------------------------------------------------------------------

mod=""

# ensure running as root or exit
if [ "$(id -u)" != "0" ]; then
  echo "run this as root or use sudo" 2>&1 && exit 1;
fi;

function pip()
{
   python -m pip $@
}

function pip3()
{
   if [ "${distro}" != "bookworm" ] && [ "${distro}" != "noble" ]
   then
      python3 -m pip $@
   else
      python3 -m pip $@ --break-system-packages
   fi
}

function apt()
{
   NEEDRESTART_MODE=a DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical \
      /usr/bin/apt --yes --quiet \
         --option Dpkg::Options::=--force-confold \
         --option Dpkg::Options::=--force-confdef \
         "$@" # &>/dev/null
}

# for older versions of Raspbian, insure that apt-transport-https is installed first
echo -n "Installing prerequisites (this might take a few minutes)..."
apt update --allow-releaseinfo-change
apt install libboost-thread-dev lsb-release

distro="bookworm"
if { uname -m | grep -q "arm"; }
then
   arch=""
else
   arch="-"`uname -m`
fi

if [[ "$distro" = "noble" ]]; then
   USE_SYSFS_GPIO=false
else 
   USE_SYSFS_GPIO=true
fi

if $USE_SYSFS_GPIO; then
   # ensure that the group 'gpio' exists
   if ! grep "^gpio" /etc/group; then
      if [ "$1" == "-y" ]
      then
         answer="YES"
      else
         echo "Group 'gpio' does not exist. This group is necessary for zymbit software to operate normally."
         read -p 'Type yes in all capital letters (YES) to create this group: ' answer <&1
      fi
      if [ "${answer}" == "YES" ]
      then
         # Add group 'gpio'
         groupadd -r gpio
      else
         echo "Quitting..."
         exit -1
      fi
      # Modify /etc/rc.local to change the group of /etc/sys/class/gpio
      if ! grep -q "chown -R root:gpio" /etc/rc.local; then
         [[ -f /etc/rc.local ]] || echo '#!/bin/sh' > /etc/rc.local
         echo "chown -R root:gpio /sys/class/gpio" >> /etc/rc.local
         echo "chmod -R ug+rw /sys/class/gpio" >> /etc/rc.local
      fi
   fi
fi

# Check for existence of udev rule
if [[ ! -f "/etc/udev/rules.d/80-gpio-noroot.rules" ]]; then
   echo "ACTION==\"add\", SUBSYSTEM==\"gpio\", PROGRAM=\"/bin/sh -c 'chown -R root:gpio /sys/\$devpath; chmod -R g+w /sys/\$devpath'\"" >> /etc/udev/rules.d/80-gpio-noroot.rules
fi

# jammy uses python3-dev; no longer supports python3-dev
if [ "${distro}" != "jammy" ] && [ "${distro}" != "bookworm" ]  && [ "${distro}" != "noble" ]
then
   apt install apt-transport-https curl libyaml-dev libssl-dev libcurl4-openssl-dev python3-pip python3-setuptools i2c-tools
else
   apt install apt-transport-https curl libyaml-dev libssl-dev libcurl4-openssl-dev python3-pip python3-setuptools python3-pycurl i2c-tools
fi
if [ "${distro}" != "focal" ] && [ "${distro}" != "bookworm" ] && [ "${distro}" != "bullseye" ] && [ "${distro}" != "jammy" ] && [ "${distro}" != "noble" ]
then
   apt install -y python3-pip
   pip install inotify || exit
   pip install pycurl progress python3-gnupg
fi
pip3 install inotify progress python3-gnupg
pip3 install pycurl &>/dev/null || exit  # Will error for bookworm; installed via apt install above
echo "done."

baseurl="https://zk-sw-repo${mod}.s3.amazonaws.com"
# import zymbit gpg key
gpg_key_url="$baseurl/apt-zymkey-pubkey.gpg"
echo -n "Importing Zymbit Packages gpg key... "
# import the gpg key
curl -L "${gpg_key_url}" 2>/dev/null | sudo gpg --dearmor --yes -o /usr/share/keyrings/zymbit.gpg
echo "done."

# add zymbit apt repo to sources list
apt_source_path="/etc/apt/sources.list.d/zymbit.list"
echo -n "Installing $apt_source_path..."
repodist="$distro"
#if [[ "$distro" == noble ]]; then
#   repodist="jammy"
#fi
echo "deb [signed-by=/usr/share/keyrings/zymbit.gpg] $baseurl/apt-repo-${repodist}${arch}/ ${repodist} main" > $apt_source_path
echo "done...Updating now."
apt update

# install our packages
echo -n "Installing Zymkey Packages..."
apt install libzymkeyssl zkbootrtc zkifc zkapputilslib zksaapps zkpkcs11 cryptsetup || exit
if [ "${distro}" != "focal" ] && [ "${distro}" != "bookworm" ] && [ "${distro}" != "bullseye" ] && [ "${distro}" != "jammy" ] && [ "${distro}" != "noble" ]
then
   pip install -U zku zk_luks &>/dev/null
fi
pip3 install -U zku zk_luks &>/dev/null
if [ "${distro}" != "focal" ] && [ "${distro}" != "bookworm" ] && [ "${distro}" != "bullseye" ] && [ "${distro}" != "jammy" ] && [ "${distro}" != "noble" ]
then
   ln -s /usr/local/lib/python2.7/dist-packages/zk_luks/__init__.py /usr/local/bin/create_zk_crypt_vol
fi

# Install example scripts
echo; echo "Installing example scripts..."
mkdir -p /usr/local/share/zymkey/examples
curl -G https://s3.amazonaws.com/zk-sw-repo/zk_app_utils_test.py > /usr/local/share/zymkey/examples/zk_app_utils_test.py
curl -G https://s3.amazonaws.com/zk-sw-repo/zk_crypto_test.py > /usr/local/share/zymkey/examples/zk_crypto_test.py

curl -G https://s3.amazonaws.com/zk-sw-repo/zk_prep_encr > /usr/local/bin/zk_prep_encr
chmod +x /usr/local/bin/zk_prep_encr

# Make sure necessary crypt packages are included
apt install cryptsetup-initramfs cryptsetup-run 

# Check for NVIDIA Xavier platform
nv_model_fn="/proc/device-tree/model"
if [ -e  ${nv_model_fn} ]
then
   if grep -q -i "Xavier" ${nv_model_fn}
   then
      # Configure the zymkey environment variables for the xavier
      echo "Configuring zymkey environment for Xavier..."
      sed -i "s/216/436/" /var/lib/zymbit/zkenv.conf
      sed -i "s/=1/=8/" /var/lib/zymbit/zkenv.conf
   fi
fi

# Debian 6.6 and later use different GPIO numbering, no long forces the base number of the main GPIO controller to be global GPIO 0.
# Need to determine correct wake pin number
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" = "$1"; }
version_to_check="6.6"
current_version=`uname -r`
if version_gt $version_to_check $current_version
then
   wake_pin=`grep GPIO4 /sys/kernel/debug/gpio | sed -r 's/[^0-9]*([0-9]*).*/\1/'`
   echo "ZK_GPIO_WAKE_PIN=$wake_pin" > /var/lib/zymbit/zkenv.conf
fi

# temp workaround to set for noble for standalone apps
if [[ "$distro" == noble ]] && [[ -z "$wake_pin" ]]
then
   if grep -q "Pi 5 Model B Rev 1.0" /sys/firmware/devicetree/base/model; then
      wake_pin=575
   elif grep -q "Pi 5 Model B Rev 1.1" /sys/firmware/devicetree/base/model; then
      wake_pin=573
   elif grep -q "Compute Module 5" /sys/firmware/devicetree/base/model; then
      wake_pin=573
   elif grep -q "Pi 4" /sys/firmware/devicetree/base/model; then
      wake_pin=516
   elif grep -q "Compute Module 4" /sys/firmware/devicetree/base/model; then
      wake_pin=516
   else
      :
   fi
   if [ -n "$wake_pin" ]; then
      echo "ZK_GPIO_WAKE_PIN=$wake_pin" > /var/lib/zymbit/zkenv.conf
   fi
fi

#jammy uses dialout group for i2c. Add zymbit to dialout group
if [[ "${distro}" == jammy ]] || [[ "$distro" == noble ]]
then
   adduser zymbit dialout
fi

systemctl restart zkifc
sync
sleep 10

# reboot
echo "Rebooting now..."
reboot

폴리글랏이란 무엇인가

아니 근데

왜 다들

요즘 서비스 아키텍처 구상하면서 큰 의문이 들었습니다. 규모가 조금이라도 큰 조직이면 자바나 스프링 강박증이라도 없었다면 높은 확률로 각자가 쓰고 있는 기술 스택이 다를 것입니다. 제가 느낀 위화감이 그것이었습니다. NodeJS, PHP, Java, Go, C++(?) 등으로 이루어진 API 및 웹소켓 서버들이 있습니다. 이 서버들에 대한 공통된 모듈에 대해 항상 큰 의문이 있었습니다.

같은 솔루션에 대한 접근이라면 같은 모듈을 써도 되는 거 아니었을까?

특정 언어의 클라이언트 라이브러리가 비교적 우수한데, 그걸 공통적으로 쓸 수 없을까?

왜 더 좋은 NIC로 바꿨는데, 네트워크 성능이 안 좋아지죠?

그러게나 말이에요. 왜 사람 힘들게 만들까

미리 말씀드리지만, 저는 네트워크 엔지니어가 아니라 백엔드 엔지니어로서 짧은 시간 안에 이슈를 체크 및 리포트 중이라 조금이라도 깊게 들어갔을 때 틀렸을 수 있으니 크로스체크 부탁드립니다.

개요

무슨 일이 있었나

어쩌다 보니 현재 있는 조직에서 특정 서비스가 Tx 성능이 피크 타임에 부족하다는 의견이 있었고, 그로 인해 해당 서비스의 서버 그룹에 대해 NIC Bonding을 하는 것으로 결정되었습니다. 그래서 Broadcom의 10G NIC 2개를 묶어서 20G로 만들고, 서비스에 투입했습니다.

아키텍처의 퀀텀

퀀텀이란 무엇인가?

퀀텀은 라틴어 ‘quantus’에서 유래했으며, ‘얼마나 많은’, ‘양의’를 의미합니다. 이외에도 사전 상에서 다음과 같은 의미를 찾을 수 있습니다.

  • (물리학) 양자: 더 이상 나눌 수 없는 에너지나 물질의 최소 단위.
  • (일반적으로) 최소량, 일정량.
  • 갑작스럽고 중요한 도약을 의미할 때도 사용

아키텍처에서의 퀀텀

아키텍처에서는 사전적 의미의 최소량의 의미를 가지고 쓰이게 됩니다. 일반적으로 한번에 배포될 수 있는 단위를 퀀텀이라고 부르게 됩니다.

최근 마이크로서비스 아키텍처 트렌드와 시스템의 복잡성 증가로 인해 퀀텀 개념은 더욱 중요해졌습니다. 빠른 비즈니스 요구사항 변화에 유연하게 대처하기 위해 각 서비스를 독립적인 퀀텀으로 설계하고 배포하는 것이 필수가 되었습니다.

Flutter 설치

개요

flutter를 설치하는 걸 어려워 하시는 분들이 적잖이 계신 것같아 작성합니다.

OS 별로 설치 방법

Windows

Script를 이용해서

Written by Gemini.

PowerShell 스크립트입니다.
아무 곳이나 get_flutter.ps1같은 파일을 만들어 아래 스크립트를 붙여넣기 하여 저장하고 실행합니다.
그럼 버전을 입력해야하는데, flutter 아카이브에서 확인 후 적절한 버전을 입력하면 됩니다.

# PowerShell 스크립트: Flutter SDK 다운로드

# --- 설정 ---
# Flutter 다운로드 URL의 기본 구조
$flutterBaseUrl = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_"
$flutterUrlSuffix = "-stable.zip"

# --- 사용자 입력 받기 ---
# 사용자에게 다운로드할 Flutter 버전 번호 요청 (예: 3.29.2)
# 사용자가 입력할 때까지 스크립트가 여기서 대기합니다.
$flutterVersionInput = Read-Host -Prompt "다운로드할 Flutter 버전을 입력하세요 (예: 3.29.2). 정확한 버전 번호는 Flutter 웹사이트에서 확인하세요"

# 입력값 유효성 검사 (간단히 비어있는지만 확인)
if ([string]::IsNullOrWhiteSpace($flutterVersionInput)) {
    Write-Error "버전 번호를 입력해야 합니다. 스크립트를 종료합니다."
    # 스크립트 비정상 종료 (오류 코드 1)
    exit 1
}

# 입력받은 버전으로 전체 다운로드 URL 구성
# 사용자가 '3.19.6'을 입력하면, URL은 ".../flutter_windows_3.19.6-stable.zip" 형태가 됩니다.
$flutterZipUrl = "$($flutterBaseUrl)$($flutterVersionInput)$($flutterUrlSuffix)"

# 다운로드 받을 임시 파일 경로 설정
$tempDir = $env:TEMP
# 파일 이름에도 버전을 포함시켜 구분 용이하게 함 (선택 사항)
$downloadFileName = "flutter_sdk_$($flutterVersionInput).zip"
$tempDownloadPath = Join-Path $tempDir $downloadFileName

# 압축 해제 대상 폴더 (사용자 홈 폴더)
$extractDestination = $HOME

# --- 스크립트 실행 ---
Write-Host "Flutter SDK v$($flutterVersionInput) 다운로드 및 설치를 시작합니다..." -ForegroundColor Cyan

# 1. Flutter SDK 다운로드
Write-Host "Flutter SDK 다운로드 중... ($flutterZipUrl)"
Write-Host "임시 저장 경로: $tempDownloadPath"
try {
    # Invoke-WebRequest를 사용하여 파일 다운로드
    Invoke-WebRequest -Uri $flutterZipUrl -OutFile $tempDownloadPath -ErrorAction Stop
    Write-Host "다운로드 완료." -ForegroundColor Green
} catch {
    # 오류 발생 시, 입력한 버전이나 URL이 잘못되었을 가능성을 명시
    Write-Error "Flutter SDK v$($flutterVersionInput) 다운로드 실패: $($_.Exception.Message)"
    Write-Error "입력한 버전 번호('$flutterVersionInput')가 정확한지, 해당 버전이 stable 채널에 존재하는지 확인하세요."
    Write-Error "시도한 URL: $flutterZipUrl"
    # 실패 시 임시 파일 삭제 (존재하는 경우)
    if (Test-Path $tempDownloadPath) {
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction SilentlyContinue
    }
    # 스크립트 중단
    exit 1
}

# 2. Flutter SDK 압축 해제
Write-Host "Flutter SDK 압축 해제 중... ($extractDestination)"
try {
    # Expand-Archive를 사용하여 zip 파일 압축 해제
    Expand-Archive -Path $tempDownloadPath -DestinationPath $extractDestination -Force -ErrorAction Stop
    Write-Host "압축 해제 완료." -ForegroundColor Green

    # 압축 해제 확인
    $expectedFlutterPath = Join-Path $extractDestination "flutter"
    if (Test-Path $expectedFlutterPath) {
        Write-Host "Flutter SDK가 '$expectedFlutterPath' 경로에 성공적으로 압축 해제되었습니다."
    } else {
        Write-Warning "'$expectedFlutterPath' 경로를 찾을 수 없습니다. 압축 해제 결과를 확인하세요."
    }

} catch {
    Write-Error "Flutter SDK 압축 해제 실패: $($_.Exception.Message)"
    # 압축 해제 실패 시에도 임시 파일 삭제
    if (Test-Path $tempDownloadPath) {
        Write-Host "압축 해제 오류 발생. 임시 파일 삭제 중: $tempDownloadPath"
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction SilentlyContinue
    }
    # 스크립트 중단
    exit 1
}

# 3. 임시 다운로드 파일 삭제
Write-Host "임시 다운로드 파일 삭제 중... ($tempDownloadPath)"
try {
    if (Test-Path $tempDownloadPath) {
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction Stop
        Write-Host "임시 파일 삭제 완료." -ForegroundColor Green
    } else {
        Write-Host "임시 파일이 이미 삭제되었거나 존재하지 않습니다."
    }
} catch {
    Write-Warning "임시 파일 '$tempDownloadPath' 삭제 실패: $($_.Exception.Message)"
}

# 추가할 경로 정의
$flutterBinPathToAdd = Join-Path $HOME "flutter\bin"
$variableName = "Path"
$variableScope = "User" # 사용자 범위 지정

# 현재 사용자 PATH 값 가져오기
$currentUserPath = [System.Environment]::GetEnvironmentVariable($variableName, $variableScope)

# 경로가 이미 포함되어 있는지 확인 (중복 추가 방지)
if ($currentUserPath -split ';' -notcontains $flutterBinPathToAdd) {
    # 기존 PATH 끝에 세미콜론(;)과 새 경로 추가
    # 기존 PATH가 비어있거나 세미콜론으로 끝나지 않는 경우 고려
    $newUserPath = ($currentUserPath, $flutterBinPathToAdd) -join ';' -replace ';{2,}', ';' # 여러 개의 세미콜론을 하나로 정리

    # 새 PATH 값 설정
    [System.Environment]::SetEnvironmentVariable($variableName, $newUserPath, $variableScope)

    Write-Host "사용자 환경 변수 '$variableName'에 '$flutterBinPathToAdd' 경로를 추가했습니다." -ForegroundColor Green
    Write-Host "변경 사항을 적용하려면 새 PowerShell 창을 열거나 로그아웃 후 다시 로그인하세요."
} else {
    Write-Host "'$flutterBinPathToAdd' 경로는 이미 사용자 '$variableName' 환경 변수에 존재합니다." -ForegroundColor Yellow
}

# --- 완료 메시지 및 다음 단계 안내 ---
Write-Host "--------------------------------------------------" -ForegroundColor Cyan
Write-Host "Flutter SDK v$($flutterVersionInput) 설치 스크립트가 완료되었습니다." -ForegroundColor Cyan
Write-Host "다음 단계를 진행하세요:"
Write-Host "- 새 PowerShell 창을 열고 'flutter doctor' 명령어를 실행하여 설치 상태를 확인하세요."
Write-Host "--------------------------------------------------" -ForegroundColor Cyan

scoop을 이용해서

Scoop은 windows에서 사용할 수 있는 패키지 매니저입니다. PowerShell에서 편하게 쓸 수 있습니다.

요청 수를 줄이고, 캐시 수용량은 늘리기

valkey?

여전히 valkey보다 redis가 더 익숙한 분들이 계실 겁니다. valkey는 Redis Inc.의 redis에 대한 라이센스 변경 및 독점적 지위 확보 시도에 대한 반발로 redis 7.2.4를 베이스로 추가 개발되고 있는 레디스 대체제입니다. 현재는 레디스보다도 먼저 8.0 버전을 출시하는 등 굉장히 활발하게 개발 및 유지보수 되고 있습니다.

당연한 이야기로 레디스의 포크인 만큼 레디스와 동일하게 다양한 곳에서 캐시로써 활용되고 있습니다. 이 글에서는 그 중 가장 보편적인 흐름 중 하나인 유저가 API 서버에 질의 후, API 서버는 캐시를 조회, 실패 시 RDB에서 조회하는 방식을 사용하는 패턴에서 효과적으로 밸키에 대한 읽기를 감소 시키는 방법을 소개하고자 합니다.

강한 일관성과 최종 일관성

잠재적인 데이터 불일치 문제

MSA는 개발 및 배포의 유연성, 기술 스택의 다양성, 확장성, 장애 격리 등 다양한 장점을 제공하지만, 그로 인해 트레이드 오프되는 단점들도 분명히 존재합니다. 그 대표적인 부분이 비즈니스 로직 오류, 사용자 경험 저하, 심각한 경우 금전적 손실까지 야기할 수 있는 중요한 문제인 잠재적인 데이터 불일치 문제일 겁니다. 이 문제는 다양한 이유로 발생할 수 있지만, 바로 생각나는 이유로는 다음과 같은 것들이 있을 겁니다.

  • 각 서비스 마다 독립적인 데이터베이스를 사용한다거나
  • EDA를 기반으로 요청과 처리를 해서 트랜잭션 처리가 어렵다거나
  • 네트워크 불안정으로 인해 흐름이 순간적으로 끊긴다거나
  • 특정 서비스의 장애로 트랜잭션 처리가 지연되거나 취소된다거나

결국 요지는 기존의 단일 데이터베이스에서 하던 트랜잭션을, 여러 서비스가 서로 다른 목적을 위해 각자의 데이터베이스에서 독립적으로 수행하다보니 연결이 느슨해지고, 그 틈새에서 발생할 수 있는 다양한 이슈로 인해 서비스 간 데이터 상태 동기화에 문제가 생길 수 있다는 것입니다.

로그 스트리밍으로 서비스 구성하기

배경

로그도 남기고, 이벤트도 남겨?

서비스를 쭉 구성하다보니 이런 생각을 했습니다.

  1. 어차피 로그는 남긴다.
  2. 필요한 유저 행동에 대해 이벤트르 발행한다.
  3. 그러면 각 서비스가 로그에서 필요한 걸 가져가서 상태 변화를 기록하면 되는 것 아닐까?
  4. 뭣하러 굳이 이벤트를 별도로 발행하지? 어차피 모두 kafka같은 메시지 브로커를 탈텐데?

왜 굳이 2개를 중복으로 메시지 브로커에게 남겨야 하는가가 매우 큰 궁금증이었습니다.
기본적으로 어떤 메시지 브로커를 선택하든 간에, 아니 가장 대표적인 Kafka를 상정하고 얘기해도, 각 컨슈머 그룹에 따라 메시지를 복제해서 전달하고, 컨슈머 그룹 내에 여러 컨슈머가 있을 경우에 적절히 분배해주는 기능이 존재합니다.
MSA를 구성하든, MMA를 구성하든 간에 사실 필요한 로그를 누락 없이 받아서 처리할 수 있을 것입니다.
처리량이 많다고요? 노드를 늘리고, 스펙을 올리죠!