기관공유데이터 관리시스템(단독형)을 구축하면서 행정정보공동이용센터 연계용 ESB 에이전트를 설치했습니다.
설치가이드는 20쪽 남짓인데, 실제로는 가이드에 없는 함정이 여섯 개쯤 있었습니다. 같은 길을 가실 분들을 위해 정리해 둡니다.
환경
- OS: RHEL 8.10 (최소 설치)
- 서버: WAS + 연계 통합 구성 (NIA 단독형 3티어 가이드 기반, 연계를 WAS에 합침)
- 컨테이너: podman (NIA 패키지 v2.1.0)
- 에이전트: ESB_AGENT 2.5
- GPKI 표준 API: gpkiapiJava v1.5.2.0 (64bit)
에이전트는 컨테이너가 아니라 호스트에 직접 설치합니다. 이 점을 처음에 헷갈렸는데, NIA 패키지의 orgstd-link 컨테이너는 연계 업무를 처리하는 쪽이고, ESB 에이전트는 그 결과물을 실어 나르는 별개의 프로그램입니다.

1. 준비물
| 파일 | 관리주체 |
|---|---|
| ESB_AGENT_2.5.tar | 행공센 유지보수사업단 |
| gpkiapiJava.tar (64bit) | 인증관리센터 |
| gpkiapi.lic (라이선스) | 인증관리센터 |
| 배치용 서버인증서 (.cer/.key) | 인증관리센터 |
주의할 점!
GPKI API는 32bit와 64bit가 따로 옵니다. 파일명 끝을 꼭 확인하세요.
gpkiapiJava_v1.5.1.0_..._32bit_....tar ← 이거 아님
gpkiapiJava_v1.5.2.0_..._64bit_....tar ← 이것
2. OS 준비
최소 설치라 필요한 게 몇 개 빠져 있었습니다.
sudo dnf install -y java-1.8.0-openjdk-headless openldap-clients cyrus-sasl-lib
JDK는 -headless로 충분합니다. 에이전트는 java -jar로만 돌고 컴파일할 일이 없거든요. -devel을 깔면 X11 라이브러리가 줄줄이 딸려옵니다.
cyrus-sasl-lib는 가이드에 없는데 반드시 필요합니다. 이유는 4장에서 설명하겠습니다.
JAVA_HOME은 링크로
ls -ld /usr/lib/jvm/jre-1.8.0-openjdk
# → /etc/alternatives/jre_1.8.0_openjdk 를 가리킴
readlink -f $(which java)로 나오는 실경로에는 버전 문자열이 박혀 있어서, JDK 패치 후 경로가 사라집니다. 버전 고정 심볼릭 링크(jre-1.8.0-openjdk)를 쓰면 패치를 자동으로 따라갑니다.
echo 'export JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk' >> ~/.bash_profile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bash_profile
3. GPKI 표준 API 설치
mkdir -p ~/gpki/api && cd ~/gpki/api
tar xf gpkiapiJava_v1.5.2.0_*64bit*.tar
ln -s gpkiapiJava_v1.5.2.0 current # 버전 바뀌어도 설정 안 고치려고
검증 스크립트 경로가 가이드와 다릅니다
가이드에는 javaDoc/jtest/class/run64.sh라고 되어 있는데, 실제로는 jtest/run.sh 입니다. 루트 바로 아래이고 class 폴더도, run64도 없습니다. 두 번 헛걸음했습니다.
cd ~/gpki/api/current/jtest
sh run.sh
4. 함정 ① — libsasl2.so.2
run.sh를 돌리자마자 이게 나왔습니다.
java.lang.UnsatisfiedLinkError: libgpkiapi.so: libsasl2.so.2:
cannot open shared object file: No such file or directory
GPKI 모듈이 RHEL 6 시절 빌드라 구버전 SASL 라이브러리(so.2)를 찾는데, RHEL 8은 so.3만 제공합니다. 게다가 최소 설치엔 그마저도 없었습니다. 그리고 ldd로는 안 잡힌다는 점입니다. 모듈이 dlopen으로 실행 시점에 로드하는 구조라 ldd | grep 'not found'가 깨끗하게 나옵니다. 그래서 반드시 run.sh를 실제로 돌려봐야 합니다.
sudo dnf install -y cyrus-sasl-lib
sudo ln -s libsasl2.so.3 /usr/lib64/libsasl2.so.2
sudo ldconfig
SASL2는 ABI가 안정적이라 호환 링크로 통합니다. 다만 OS 라이브러리 폴더에 링크를 추가한 조치라, OS 재설치 후엔 다시 해야 합니다.
참고로 OpenSSL은 걱정 안 하셔도 됩니다. el6 빌드라 1.0 계열을 찾을 줄 알았는데, ldd 결과 1.1에 링크되어 있어서 RHEL 8 기본으로 충족됐습니다. 가이드가 요구하는 openssl-1.0.2k 소스는 결국 안 썼습니다.
5. 함정 ② — 라이선스가 디렉터리
인증관리센터에서 받은 license 항목이 파일이 아니라 디렉터리였습니다. 진짜 라이선스 파일은 그 안에 있습니다.
license/
└── gpkiapi.lic ← 이게 진짜
license_temp/
└── gpkiapi.lic ← 임시 라이선스
cp -r license ~/esb_agent/lic/gpkiapi.lic 했다가 gpkiapi.lic이라는 이름의 디렉터리가 생겨서 한참 헤맸습니다.
mkdir -p ~/esb_agent/{lic,certs}
cp ~/install/license/gpkiapi.lic ~/esb_agent/lic/
cp ~/install/cert/*.cer ~/install/cert/*.key ~/esb_agent/certs/
chmod 600 ~/esb_agent/certs/*.key
인증서는 _env(암호화)·_sig(서명) 각각 .cer+.key로 4개 한 세트입니다. 배치용 서버인증서는 CN이 정보유통 ID와 같아야 합니다.
openssl x509 -in ~/esb_agent/certs/*_sig.cer -noout -subject -enddate
6. 함정 ③ — 라이선스 IP 불일치
run.sh를 다시 돌리니 에러 코드가 바뀌었습니다.
ErrCode=1100 Load license is failed → 파일 못 찾음 (jtest/에 복사하면 해결)
ErrCode=1111 Match IP does not exist → 파일은 읽었는데 IP가 안 맞음
GPKI 라이선스는 IP 귀속입니다. 그런데 신청서에 적은 IP가 NAT 접속 주소였고, 서버의 실제 NIC 주소는 달랐습니다.
hostname -I
# 10.x.x.x 10.x.x.x ← 앞이 실제 NIC, 뒤는 podman 브리지
GPKI API는 서버 커널이 아는 로컬 IP만 검사합니다. NAT 장비에 붙은 주소는 서버가 모릅니다. 인증관리센터에 로컬 IP 기준으로 재발급 받으니 해결됐습니다.
신청서 쓸 때
ip -4 addr로 실제 주소를 확인하고 적으세요. 접속 주소를 적으면 저처럼 재발급 받아야 합니다.
두번째 IP는 podman이 만든 가상 브리지라 어디에도 쓰면 안 됩니다.
7. 에이전트 설정
mkdir -p ~/esb_agent && cd ~/esb_agent
tar xf ~/install/ESB_AGENT_2.5.tar
vi agent.properties
im.name은 IM_기관구분코드_정보유통ID 형식이고, 기관구분코드는 CO/GO/LG/BO 중 하나입니다. 저희 경우 배포본에 이미 기관값이 채워져 있었습니다.
im.host에는 서버 로컬 IP를 넣습니다. NAT 주소를 넣으면 바인딩 실패합니다.
송수신 폴더
설치환경요약서에 제출한 경로 그대로 만듭니다. 이 경로는 행공센이 어댑터를 생성할 때 고정되므로 제출 후 바꾸려면 재요청입니다.
sudo mkdir -p /share_data/esb/{send/work,recv/work}
sudo chown -R 1000:1000 /share_data/esb
/share_data가 컨테이너 볼륨이라 호스트에 없을 수 있습니다. 저희는 bind mount로 노출했습니다.
echo "/home/was_user/v2.1.0/volumes/share_data /share_data none bind 0 0" | sudo tee -a /etc/fstab
sudo mount -a
8. systemd 등록 — 함정 ④⑤
가이드에는 im.sh start 수동 실행만 있고 자동 기동 절차가 없습니다. 재부팅하면 에이전트가 안 뜹니다. 유닛을 직접 만들었는데, 여기서 두 번 더 걸렸습니다.
[Unit]
Description=ESB Agent (IM)
After=network-online.target
RequiresMountsFor=/share_data
[Service]
Type=forking
User=was_user
WorkingDirectory=/home/was_user/esb_agent/im/bin
Environment=JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk
Environment=PATH=/usr/lib/jvm/jre-1.8.0-openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=LD_LIBRARY_PATH=/home/was_user/gpki/api/current/lib64
ExecStart=/home/was_user/esb_agent/im/bin/im.sh start
ExecStop=/home/was_user/esb_agent/im/bin/im.sh stop
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
함정 ④ — PATH 줄이 필수입니다. im.sh가 JAVA_HOME을 안 보고 PATH의 java만 찾습니다. systemd는 .bash_profile을 읽지 않으니, PATH를 안 주면 java: command not found로 죽습니다. 수동으로는 되는데 재부팅 후에만 실패하는 전형적인 원인입니다.
함정 ⑤ — 종료 코드 143. 에이전트가 정상 종료할 때 143(SIGTERM)을 반환하는데, 이걸 성공으로 선언하지 않으면 systemctl stop을 해도 failed로 찍힙니다. 서비스는 멀쩡한데 상태만 실패로 보이는 거라, SuccessExitStatus=143 한 줄이면 해결됩니다.
PID 파일 위치 주의
im.sh가 PID 파일을 $HOME/im.pid에 쓰는데, systemd로 띄우면 $HOME이 작업 디렉터리로 잡혀 im/bin/im.pid에 생깁니다. 수동 기동과 systemd 기동을 섞어 쓰면 서로 stop이 안 됩니다. 한 가지로 통일하세요.
비정상 종료 후 InstanceManager Already Running!!이 나오면 잔존 PID 파일 때문입니다.
ps -ef | grep IM # 프로세스 없는 것 확인
rm -f ~/esb_agent/im/bin/im.pid
sudo systemctl start esb-agent
9. 기동 확인
sudo systemctl enable --now esb-agent
ps -ef | grep startup.jar
IM이 뜬 뒤 행공센에서 어댑터가 내려오면 프로세스가 IM 1개 + 어댑터 2개(송신·수신) 로 늘어납니다. 어댑터는 우리가 만드는 게 아니라 행공센이 요약서 기준으로 생성해 원격 배포합니다.
ls ~/esb_agent/adaptor/log/ # AD_..._SND_01 / AD_..._RCV_01 폴더 생기면 배포 완료
마치며 — 삽질 요약
| 함정 | 증상 | 해결 |
|---|---|---|
| jtest 경로 | 가이드와 다름 | jtest/run.sh |
| libsasl2.so.2 | UnsatisfiedLinkError | cyrus-sasl-lib + 호환 링크 |
| license가 디렉터리 | gpkiapi.lic이 폴더가 됨 | 안의 파일을 복사 |
| 라이선스 IP | ErrCode=1111 | 로컬 IP로 재발급 |
| systemd PATH | 재부팅 후만 실패 | Environment=PATH= |
| 종료 코드 143 | stop해도 failed | SuccessExitStatus=143 |
가이드만 믿고 갔으면 일주일은 더 걸렸을 것 같습니다. 특히 ldd가 깨끗해도 실행은 실패할 수 있다는 것과, 라이선스는 접속 주소가 아니라 NIC 주소를 본다는 두 가지는 꼭 기억해 두시면 좋겠습니다.

