FTP? Нет, не слышал
Tags: nomad, ftp, docker, webdev
Published: 2022-09-20 06:28:10

Сейчас будет немного боли и радости от победы над [FTP](https://ru.wikipedia.org/wiki/FTP). Мы много и упорно работаем со [СМЭВ3](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D0%BC%D0%B5%D0%B6%D0%B2%D0%B5%D0%B4%D0%BE%D0%BC%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE_%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE_%D0%B2%D0%B7%D0%B0%D0%B8%D0%BC%D0%BE%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D1%8F). Никакой магии: обычный SOAP поверх HTTP, быстро, надёжно - все банки и гос. учреждения знают, как это делается. И вдруг (никогда такого не было, и вот опять) оказалось, что нам надо забирать большие файлы с FTP, предоставляемого СМЭВ.

> Дисклеймер: постарайтесь воздерживаться от использования FTP - это технология немного устарела и не отвечает современным требованиям удобства и безопасности.

Тем не менее, надо - значит надо. СМЭВ располагается в защищенной инфраструктуре электронного правительства, куда мы ходим через шлюзы, расположенные в определённой инфраструктуре. В этих шлюзах нет ничего необычного - HTTP, балансировка, отказоустойчивость через избыточность.  

## В чём проблема?
Шлюзы есть, прокси есть, с чего бы нам бояться [FTP](https://ru.wikipedia.org/wiki/FTP)? Оказывается, бояться есть с чего - FTP использует не один порт, а множество динамически открываемых, причём есть особенный режим (active), когда сервер инициирует соединение к клиенту - это еще со стародавних времён, когда никакого NAT не было. 

В результате мы не можем работать с этим FTP "по простому" - нам требуется как-то обеспечить сетевую связность и наши любимые и привычные инструменты нам в этом помочь не могут 😠 . Надо думать.

> FTP (англ. File Transfer Protocol) — протокол передачи файлов по сети, появившийся в 1971 году задолго до HTTP и даже до TCP/IP, благодаря чему является одним из старейших прикладных протоколов. Изначально FTP работал поверх протокола NCP, на сегодняшний день широко используется для распространения ПО и доступа к удалённым хостам. 

Дополнительной особенностью в нашем случае является не просто наличие шлюза, через который необходимо обращаться в СМЭВ, а целых 2:

- Gateway 1 - внутри облака
- Gateway 2 - в специальной инфраструктуре с сетевой связностью со СМЭВ 

вот так выглядит наш каскад проксей:
![proxy cascade](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfy0d3uo0ucyhrns2w7k.png)

## Решение
Если есть проблема, то должно быть и решение. Так и оказалось - есть несколько (наверное даже много) программных продуктов, связанных с проксированием FTP:

- [3proxy](https://3proxy.ru/) - целый набор проксей. Прочитали доку, запустили - не завелось.
- [Squid](https://en.wikipedia.org/wiki/Squid_(software)) - большой, надёжный, кеширующий прокси, на все случаи жизни. Для нас оказался слишком избыточен в настройке. И в контексте FTP практически нет документации.
- [ftp-proxy](http://tdkare.ru/sysadmin/index.php/Ftp-proxy) (ftpproxy) - стандартный пакет в дистрибутиве Debian/Ubuntu, и он взлетел со свитом.

Поскольку всё наше облако оркестрируется с помощью [Nomad](https://www.nomadproject.io/), то и этот компонент мы завернули docker.

Dockerfile
```dockerfile
FROM debian:bullseye-slim

RUN apt-get update 
RUN apt-get install -y \
        ftp-proxy gettext

RUN mkdir -p /tmp/logs/
ADD ./ftp-proxy.conf.in /tmp/ftp-proxy.conf.in

ENV DESTINATION_ADDRESS=127.0.0.1
ENV DESTINATION_PORT=21
ENV PROXY_PORT=21

ENV PROXY_LOG_LEVEL=DBG

EXPOSE 21
CMD envsubst < /tmp/ftp-proxy.conf.in > /etc/proxy-suite/ftp-proxy.conf && exec /usr/sbin/ftp-proxy -n -d 
ftp-proxy.conf
```

В конфиге можно задать миллион других настроек, но в нашем случае достаточно только этого:
```
[-Global-]

DestinationAddress      ${DESTINATION_ADDRESS}
DestinationPort         ${DESTINATION_PORT}

Port                    ${PROXY_PORT}

LogDestination          | cat
LogLevel                ${PROXY_LOG_LEVEL}
```

## nomad
Мы катаем джобы с помощью ансибла и используем его шаблонизацию, так что тут я приведу уже финальный результат препроцессинга:

```hcl
variable "instance" {
  type    = string
  default = "prod"
}

job "ftp-proxy-prod" {
  datacenters = ["zone-a", "zone-b", "zone-c"]
  namespace   = "default"

  constraint {
    attribute = "${meta.gate}"
    value     = "true"
  }

  update {
    max_parallel      = 1
    health_check      = "checks"
    min_healthy_time  = "10s"
    healthy_deadline  = "3m"
    progress_deadline = "6m"
    auto_revert       = true
    auto_promote      = false
    canary            = 0
  }

  type = "system"

  group "ftp-proxy" {

    network {
      port "ftp" {
        static = 21
        host_network = "private"
      } 
    }

    task "ftp-proxy-prod" {
      driver = "docker"
      kill_timeout = "30s"
      leader = true

      env {
        DESTINATION_ADDRESS = "10.0.0.34"
        DESTINATION_PORT = "8878"
        PROXY_PORT = "21"
        PROXY_LOG_LEVEL = "DBG"
      }

      restart {
        attempts  = 3
        interval  = "5m"
        delay     = "15s"
        mode      = "fail"
      }

      config {
        image = "YOURIMAGE:latest"
        force_pull = true
        network_mode = "host"

        auth {
          username = "USER"
          password = "PASS"
        }
      }

      service {
        name = "prod-ftp"
        tags = [ "ftp-proxy", "prod" ]
        
        port = "ftp"
        
        check {
          type = "tcp"
          port = "ftp"
          interval = "2s"
          timeout = "5s"
        }        
        
        check_restart {
          limit = 3
          grace = "10s"
          ignore_warnings = false
        }        
      }

      logs {
        max_files     = 5
        max_file_size = 50
      }

      resources {
        memory = 100
        memory_max = 200
        cpu = 200
      }
    }
  }


}

```

```bash
nomad job run -detach ftp-proxy.hcl
```

## Конец
Всем спасибо - всё работает, но если у кого-то есть замечания, связанные с данным решением - мы с огромным интересом их рассмотрим 🤞.