Пример docker-compose для тестовой SMS-лаборатории (SMPP сервер + клиент)

Обновлено и опубликовано Опубликовано:

Используемые термины: SMPP, Docker.

В данной инструкции мы развернем в docker небольшой стенд, состоящий из SMSC (SMPP-сервер) и Kannel — ESME (клиент). Такая лаборатория пригодится для отладки интеграции с SMS-агрегатором до подключения боевого канала — можно проверить формат запросов, склейку длинных сообщений и обработку DLR без затрат на реальную отправку. При этом, реальный оператор не нужен, все SMS живут внутри тестовой среды.

Стенд только для тестов, в публичную сеть порты лучше не открывать.

Файл docker-compose.yml

Создадим каталог для рабочих файлов и конфигов. Сразу перейдем в него:

mkdir -p /opt/smpp-lab/conf && cd /opt/smpp-lab

Соберем три сервиса вместе — SMPPSim, bearerbox и smsbox — и опишем зависимости между ними:

vi docker-compose.yml

services:

  smppsim:
    image: eagafonov/smppsim:latest
    container_name: smppsim
    hostname: smppsim
    restart: unless-stopped
    environment:
      TZ: Europe/Moscow
    ports:
      - "2775:2775"
      - "8884:88"
    volumes:
      - ./conf/smppsim.props:/opt/local/SMPPSim/conf/smppsim.props:ro
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:88 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s

  bearerbox:
    build:
      context: .
      dockerfile: Dockerfile.kannel
    image: smpp-lab-kannel:local
    container_name: kannel-bearerbox
    hostname: bearerbox
    restart: unless-stopped
    command: ["/usr/sbin/bearerbox", "-v", "0", "/etc/kannel/kannel.conf"]
    environment:
      TZ: Europe/Moscow
    ports:
      - "13000:13000"
    volumes:
      - ./conf/kannel.conf:/etc/kannel/kannel.conf:ro
    depends_on:
      smppsim:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:13000/status?password=kannel || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 15s

  smsbox:
    image: smpp-lab-kannel:local
    container_name: kannel-smsbox
    hostname: smsbox
    restart: unless-stopped
    command: ["/usr/sbin/smsbox", "-v", "0", "/etc/kannel/kannel.conf"]
    environment:
      TZ: Europe/Moscow
    ports:
      - "13013:13013"
    volumes:
      - ./conf/kannel.conf:/etc/kannel/kannel.conf:ro
    depends_on:
      bearerbox:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://bearerbox:13000/status?password=kannel | grep -q \"smsbox:smsbox\" || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s

* где:

  • eagafonov/smppsim — готовый SMPPSim (Selenium Software); конфиг монтируем в /opt/local/SMPPSim/conf/smppsim.props.
  • 2775 — SMPP с хоста; ваш код/клиент биндится на 127.0.0.1:2775 как smppclient2 / password.
  • 8884:88 — веб SMPPSim на хосте 8884, внутри контейнера слушает 88 (штатный HTTP_PORT образа).
  • bearerbox — ядро Kannel, держит SMPP-сессию с симулятором.
  • smsbox — HTTP /cgi-bin/sendsms на 13013; стартует после живого status у bearerbox.
  • smsbox-port 13001 — только внутри сети compose, на хост не публикуем.

Конфигурационные файлы

Создадим 2 конфигурации для сервера и клиента, а также dockerfile для kannel.

SMPPSim (сервер)

Опишем настройки симулятора:

vi conf/smppsim.props

SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=10
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.DeterministicLifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=2000
MAX_TIME_ENROUTE=3000
DELAY_DELIVERY_RECEIPTS_BY=1000
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=88
HTTP_THREADS=1
DOCROOT=www
AUTHORISED_FILES=/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/pda2.gif,/user-guide.htm,/css/style.css,/images/dots.gif,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2
PASSWORDS=password,password
OUTBIND_ENABLED=false
DELIVERY_MESSAGES_PER_MINUTE=0
LOOPBACK=FALSE
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=100
DECODE_PDUS_IN_LOG=true
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim

* где:

  • SMPP_PORT — порт SMSC для BIND/SUBMIT_SM/DELIVER_SM.
  • SYSTEM_IDS / PASSWORDS — пары логин/пароль ESME; Kannel берет smppclient1, второй слот — для внешнего SMPP-клиента.
  • DeterministicLifeCycleManager — финальный статус SMS задает первая цифра номера получателя: 1 EXPIRED, 2 DELETED, 3 UNDELIVERABLE, 4 ACCEPTED, 5 REJECTED, любая другая — DELIVERED.
  • HTTP_PORT — веб-морда внутри контейнера (инъекция MO); на хост публикуем как 8884:88.
  • LOOPBACK=FALSE — исходящее SMS не возвращается обратно как входящее; для эха MO поставьте TRUE.
  • DECODE_PDUS_IN_LOG — в логах будут разборы PDU, а не только hex.

Конфигурация Kannel

Опишем настройки bearerbox, smsbox и подключение к SMSC:

vi conf/kannel.conf

group = core
admin-port = 13000
smsbox-port = 13001
admin-password = kannel
status-password = kannel
admin-allow-ip = "*.*.*.*"
box-allow-ip = "*.*.*.*"
log-file = "/var/log/kannel/bearerbox.log"
log-level = 0
access-log = "/var/log/kannel/access.log"

group = smsc
smsc = smpp
smsc-id = smppsim
host = smppsim
port = 2775
transceiver-mode = true
smsc-username = smppclient1
smsc-password = password
system-type = VMA
interface-version = 34
enquire-link-interval = 30
msg-id-type = 0
throughput = 20
source-addr-ton = 1
source-addr-npi = 1
dest-addr-ton = 1
dest-addr-npi = 1

group = smsbox
smsbox-id = smsbox
bearerbox-host = bearerbox
sendsms-port = 13013
sendsms-interface = 0.0.0.0
global-sender = 1234
mo-recode = true
log-file = "/var/log/kannel/smsbox.log"
log-level = 0

group = sendsms-user
username = tester
password = foobar
concatenation = true
max-messages = 10
default-smsc = smppsim
forced-smsc = smppsim
user-allow-ip = "*.*.*.*"

group = sms-service
keyword = default
text = "lab-echo: %a"
max-messages = 1
catch-all = true

* где:

  • admin-allow-ip / box-allow-ip — без маски *.*.*.* статус с хоста и smsbox из соседнего контейнера не достучатся (по умолчанию только 127.0.0.1).
  • host = smppsim — DNS-имя сервиса в compose, не localhost.
  • transceiver-mode — один BIND_TRANSCEIVER вместо пары transmitter+receiver; иначе DLR и MO не придут на этот bind.
  • smsc-username / smsc-password — должны совпасть с первой парой в smppsim.props.
  • msg-id-type = 0 — message_id в submit_sm_resp как hex-строка; иначе Kannel не склеит DLR с исходным SMS.
  • throughput — потолок MT в секунду на стороне Kannel; имитация лимита агрегатора.
  • concatenation / max-messages — длинный текст режется на сегменты с UDH.
  • sms-service — ответ на входящее MO; %a подставит текст входящего.

Dockerfile Kannel

Готового образа с нужной версией Kannel под задачу нет, поэтому соберем свой на базе Debian:

vi Dockerfile.kannel

FROM debian:bookworm-slim

ENV DEBIAN_FRONTEND=noninteractive TZ=Europe/Moscow

RUN printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d \
 && chmod +x /usr/sbin/policy-rc.d \
 && apt-get update \
 && apt-get install -y --no-install-recommends kannel curl ca-certificates \
 && mkdir -p /var/log/kannel \
 && chown -R kannel: /var/log/kannel /etc/kannel \
 && chmod 755 /var/log/kannel /etc/kannel \
 && rm -rf /var/lib/apt/lists/*

WORKDIR /etc/kannel

USER kannel

* где:

  • policy-rc.d exit 101 — пакет kannel не пытается поднять init-скрипты внутри образа.
  • kannel — пакет Debian с бинарниками /usr/sbin/bearerbox и /usr/sbin/smsbox.
  • curl — для healthcheck в compose.
  • CMD не задается, так как образ будет использоваться в docker-compose для двух сервисов. Каждый из них будет запускать процесс своей командой.

Запуск контейнеров и проверка статуса

Соберем образ Kannel и поднимем стек:

docker-compose build

docker-compose up -d

Проверим состояние контейнеров:

docker-compose ps

* все три сервиса в состоянии Up; у bearerbox healthcheck — healthy.

Стек поднят. Запросим статус bearerbox и убедимся, что он подключился к симулятору:

curl -sS "http://127.0.0.1:13000/status?password=kannel"

В выводе ищем SMSC smppsim в статусе online / connected:

SMSC connections:
    smppsim[smppsim]    SMPP:smppsim:2775/2775:smppclient1:VMA (online 89s, rcvd: sms 0 (0.00,0.00,0.00) / dlr 0 (0.00,0.00,0.00), sent: sms 0 (0.00,0.00,0.00) / dlr 0 (0.00,0.00,0.00), failed 0, queued 0 msgs)

* если still connecting — 10–20 секунд на BIND, затем повторяем запрос.

Проверим логи обоих сервисов:

docker-compose logs --tail=80 smppsim

docker-compose logs --tail=80 bearerbox

* в логах симулятора должно быть BIND_TRANSCEIVER и успешный bind для smppclient1; в bearerbox — подключение к SMSC smppsim.

Bind установлен, можно отправлять сообщения.

Отправка SMS

Отправим тестовое сообщение через HTTP API Kannel:

curl -sS -G "http://127.0.0.1:13013/cgi-bin/sendsms" --data-urlencode "username=tester" --data-urlencode "password=foobar" --data-urlencode "from=1234" --data-urlencode "to=79001234567" --data-urlencode "text=Hello SMPP lab"

* ответ Kannel мы должны увидеть код: 0: Accepted for delivery. Номер на 7 у детерминированного lifecycle — финальный статус DELIVERED.

Для кириллицы понадобятся отдельные параметры кодировки:

curl -sS -G "http://127.0.0.1:13013/cgi-bin/sendsms" --data-urlencode "username=tester" --data-urlencode "password=foobar" --data-urlencode "from=1234" --data-urlencode "to=79001234567" --data-urlencode "charset=utf-8" --data-urlencode "coding=2" --data-urlencode "text=Привет лаборатория"

* где:

  • charset=utf-8 — как передаем текст в HTTP.
  • coding=2 — UCS-2 в SUBMIT_SM; без этого кириллица уходит GSM 7-bit и превращается в «???».

Проверим склейку длинного сообщения на сегменты:

curl -sS -G "http://127.0.0.1:13013/cgi-bin/sendsms" --data-urlencode "username=tester" --data-urlencode "password=foobar" --data-urlencode "from=1234" --data-urlencode "to=79001234567" --data-urlencode "charset=utf-8" --data-urlencode "coding=2" --data-urlencode "text=Длинное SMS для проверки склейки сегментов: Kannel режет текст на части, в SUBMIT_SM появляется UDH, симулятор принимает каждый сегмент отдельно."

* в логах smppsim должно быть несколько SUBMIT_SM подряд и ненулевой esm_class / UDH у сегментов.

docker-compose logs --tail=100 smppsim

* ищем SUBMIT_SM, текст/UCS-2 и message id в submit_sm_resp.

Отчеты DLR

В двух словах, отчет DLR — это ответ от сервера клиенту о конечном статусе доставки сообщения. То есть, по нему мы можем с большой уверенностью сказать, была ли выполнена доставка или нет.

Отправим сообщение с запросом отчета о доставке и укажем адрес, куда Kannel пришлет DLR:

curl -sS -G "http://127.0.0.1:13013/cgi-bin/sendsms" --data-urlencode "username=tester" --data-urlencode "password=foobar" --data-urlencode "from=1234" --data-urlencode "to=79001234567" --data-urlencode "text=DLR delivered" --data-urlencode "dlr-mask=31" --data-urlencode "dlr-url=http://smsbox:13013/dlr-receiver?id=%F&status=%d"

Проверим и сценарий недоставки — для этого достаточно поменять первую цифру номера получателя:

curl -sS -G "http://127.0.0.1:13013/cgi-bin/sendsms" --data-urlencode "username=tester" --data-urlencode "password=foobar" --data-urlencode "from=1234" --data-urlencode "to=39990000001" --data-urlencode "text=DLR failed" --data-urlencode "dlr-mask=31" --data-urlencode "dlr-url=http://smsbox:13013/dlr-receiver?id=%F&status=%d"

* где:

  • dlr-mask=31 — все классы отчетов (delivered / failed / buffered / submit / reject).
  • 79001234567 — DELIVERED, через ~1–3 с в логах DELIVER_SM с receipt DELIVRD.
  • 39990000001 — первая цифра 3 → UNDELIVERABLE / UNDELIV.

Проверим логи bearerbox:

docker-compose logs --tail=80 bearerbox

* dlr-url в примерах выше указывает на demo-путь smsbox:13013/dlr-receiver, которого нет в конфиге smsbox — Kannel отправит запрос и получит 404. Для реальной обработки DLR нужен свой HTTP-приемник на этом адресе; в рамках теста достаточно смотреть статус доставки в логах bearerbox, как показано выше.

Остановка и очистка

Для остановки стенда без удаления данных достаточно остановить контейнеры:

docker-compose stop

Если лаборатория больше не нужна, удалим контейнеры и сеть вместе с образом Kannel:

docker-compose down --rmi local

Решение проблем

Рассмотрим несколько примеров ошибок, с которыми можно столкнуться при развертывании стенда.

SMSC still connecting / Bind Failed

Сообщения не уходят, а в логах мы видим что-то на подобие:

SMSC still connecting

Причина: не совпали system_id/password либо Kannel ходит на localhost, а не на сервис smppsim.

Решение: сверить smsc-username/smsc-password с SYSTEM_IDS/PASSWORDS; host должен быть smppsim. Пересоздать стек:

docker-compose down && docker-compose up -d

0: Accepted for delivery, DLR нет

При отправке сообщения мы получаем статус, подтверждающий отправку, но нет отчета DLR.

Причина: неверный msg-id-type или bind не transceiver.

Решение: оставить msg-id-type = 0 и transceiver-mode = true, в sendsms передавать dlr-mask=31.

Кириллица в логах как ???

Если сообщение было отправлено на русском, то в логах мы видим вместо текста различные закорючки.

Причина: HTTP отдал UTF-8, а в SMPP ушел GSM 7-bit (coding=0).

Решение: в sendsms всегда charset=utf-8 и coding=2.

Authorization failed for sendsms

При попытке отправить сообщение мы видим ошибку:

Authorization failed for sendsms

Причина: не те username/password HTTP-пользователя или IP не в user-allow-ip.

Решение: tester / foobar и user-allow-ip = "*.*.*.*".

# DevOps # Контейнеризация # Телефония
Дмитрий Моск — частный мастер
Был ли вам полезен этот скрипт?

Да            Нет

Дмитрий Моск
— IT-специалист.
Настройка серверов, услуги DevOps.

Заказать настройку контейнеризации

Нужна бесплатная консультация?

Скрипты

Пример docker-compose для развертывания тестовой SMS-лаборатории

PostgreSQL StatefulSet в Kubernetes на одном хосте

GitLab CE в Docker Compose за Traefik с HTTPS

Пример манифеста для запуска Swagger в Kubernetes

Пример скрипта для миграции запущенной виртуальной машины Proxmox с ZFS репликацией

Пример манифеста для развертывания Hermes AI в Kubernetes

Пример файла docker-compose для развертывания брокера Kafka

Другие скрипты

Все статьи

Нужен скрипт? Опишите его назначение:





Реклама