Docker и костыли в продакшене



    Навеяно публикацией «Понимая Docker», небольшой пример костылей вокруг докера для запуска веб-приложений.

    Я пробовал разные технологии обвязок, но некоторые (fig) выглядят несколько корявыми для применения, а некоторые (kubernetis, mesos) — слишком абстрактными и сложными.

    В моей конфигурации есть несколько машин, на машинах выполняются разнообразные веб-приложения, некоторые из них требуют наличия локального хранилища. В качестве базовой схемы примем конфигурацию из двух фронтендов и одного бекенда, ceph (ФС) обеспечивает роуминг данных для бекенда там, где это необходимо.

    У машин есть приватный сетевой интерфейс. У фронтендов есть еще и публичный.

    Дня конфигурации я использую связку из etcd+skydns (обнаружение сервисов), runit (мониторинг состояния контейнеров) и ansible (конфигурация). Вот код модуля ansible, который я буду обсуждать:

    много кода
    #!/usr/bin/env python
    
    import os, sys
    from string import Template
    
    def on_error(msg):
      def wrap(f):
        def wrapped(self, module):
          try:
            return f(self, module)
          except Exception, e:
            module.fail_json(msg="%s %s: %s" % (msg, self.name, str(e)))
        return wrapped
      return wrap
    
    class Service:
      SERVICE_PREFIX = 'docker-'
      SERVICES_DIR = '/etc/sv'
      RUNNING_SERVICES_DIR = '/etc/service'
    
      def __init__(self, name, image, args, announce, announce_as, port):
        self.name = name
        self.image = image
        if args is not None:
          self.args = args
        else:
          self.args = ''
        self.announce = announce
        self.announce_as = announce_as
        self.port = port
    
      def _needs_etcd(self):
        return self.announce is not None
    
      def _service_name(self):
        return self.SERVICE_PREFIX + self.name
    
      def _root_service_dir(self):
        return os.path.join(self.SERVICES_DIR, self._service_name())
    
      def _announced_service_dir(self):
        return os.path.join(self._root_service_dir(), 'services', 'service')
    
      def _etcd_service_dir(self):
        return os.path.join(self._root_service_dir(), 'services', 'announce')
    
      def _run_service_link(self):
        return os.path.join(self.RUNNING_SERVICES_DIR, self._service_name())
    
      def _root_run_file(self):
        return os.path.join(self._root_service_dir(), 'run')
    
      def _announced_service_run_file(self):
        return os.path.join(self._announced_service_dir(), 'run')
    
      def _etcd_run_file(self):
        return os.path.join(self._etcd_service_dir(), 'run')
    
      def exists(self):
        return os.path.isdir(self._root_service_dir())
    
      def scheduled_to_run(self):
        return os.path.exists(self._run_service_link())
    
      @on_error("Error starting service")
      def start(self, module):
        if self._needs_update(module):
          self.install(module)
        if self.scheduled_to_run():
          return False
        os.symlink(self._root_service_dir(), self._run_service_link())
        return True
    
      @on_error("Error stopping service")
      def stop(self, module):
        if not self.scheduled_to_run():
          return False
        os.unlink(self._run_service_link())
        return True
    
      @on_error("Error installing service")
      def install(self, module):
        if self._needs_update(module):
          self.stop(module)
          self.remove(module)
    
          self._create_service(module)
          return True
        else:
          return False
    
      @on_error("Error creating service")
      def _create_service(self, module):
        self._create_service_dirs(module)
        self._write_run_file(self._root_run_file(), self._render_root_run())
        if self._needs_etcd():
          self._write_run_file(self._announced_service_run_file(), self._render_service_run())
          self._write_run_file(self._etcd_run_file(), self._render_etcd_run())
    
      def _write_run_file(self, name, content):
        f = open(name, 'w')
        f.write(content)
        os.fchmod(f.fileno(), 0755)
        f.close()
    
      @on_error("Error verifying service existence")
      def _needs_update(self, module):
        if self.exists():
          if os.path.exists(self._root_run_file()):
            root_run = self._render_root_run()
            curr_run = open(self._root_run_file()).read()
            if root_run != curr_run:
              return True
            if self._needs_etcd():
              if os.path.exists(self._announced_service_run_file()):
                service_run = self._render_service_run()
                curr_run = open(self._announced_service_run_file()).read()
                if service_run != curr_run:
                  return True
                if os.path.exists(self._etcd_run_file()):
                  etcd_run = self._render_etcd_run()
                  curr_run = open(self._etcd_run_file()).read()
                  if etcd_run != curr_run:
                    return True
                else:
                  return True
              else:
                return True
          else:
            return True
        else:
          return True
        return False
    
      @on_error("Error creating service directory")
      def _create_service_dirs(self, module):
        os.mkdir(self._root_service_dir(), 0755)
        if self._needs_etcd():
          os.mkdir(os.path.join(self._root_service_dir(), 'services'), 0755)
          os.mkdir(self._announced_service_dir(), 0755)
          os.mkdir(self._etcd_service_dir(), 0755)
    
      @on_error("Error removing service")
      def remove(self, module):
        if not self.exists():
          return False
    
        if self.scheduled_to_run():
          self.stop(module)
    
        from shutil import rmtree
        rmtree(self._root_service_dir())
        return True
    
      def _render_root_run(self):
        if self._needs_etcd():
          return self._render_runsv_run()
        else:
          return self._render_service_run()
    
      def _render_service_run(self):
        args = self.args
        if self.announce:
          if self.port is not None:
            port = self.port
          else:
            port = self.announce
          if self.announce_as != 'container':
            args += " -p $ANNOUNCE_IP:" + self.announce + ":" + port
        return Template("""#!/bin/bash
    
    CONTAINER_NAME=$name
    
    ifconfig eth1 >/dev/null 2>&1
    if [[ $$? -eq 0 ]]; then
      PUBILC_IF=eth0
      PRIVATE_IF=eth1
    else
      PUBILC_IF=eth0
      PRIVATE_IF=eth0
    fi
    
    case "$announce_as" in
      public)  ANNOUNCE_IP="`ifconfig $$PUBILC_IF | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\\2/p'`"
               ;;
      private) ANNOUNCE_IP="`ifconfig $$PRIVATE_IF | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\\2/p'`"
               ;;
            *) ANNOUNCE_IP=""
               ;;
    esac
    
    docker inspect $$CONTAINER_NAME|grep State >/dev/null 2>&1
    if [ $$? -eq 0 ]; then
      docker rm $$CONTAINER_NAME || { echo "cannot remove container $$CONTAINER_NAME"; exit 1; }
    fi
    
    docker pull $image
    
    exec docker run \
    -i --rm \
    --name $$CONTAINER_NAME \
    --hostname "`hostname`-$name" \
    $args \
    $image
    """).substitute(name=self.name, image=self.image, args=args, announce_as=self.announce_as)
    
      def _render_runsv_run(self):
        return """#!/bin/bash
    
    runsvdir -P services &
    RUNSVPID=$!
    
    trap "{ sv stop `pwd`/services/*; sv wait `pwd`/services/*; kill -HUP $RUNSVPID ; exit 0; }" SIGINT SIGTERM
    
    wait
    """
    
      def _render_etcd_run(self):
        return Template("""#!/bin/bash
    
    ETCD="http://192.0.2.1:4001"
    DOMAIN="com/example/prod/s/$name/`hostname`"
    
    ifconfig eth1 >/dev/null 2>&1
    if [[ $$? -eq 0 ]]; then
      PUBILC_IF=eth0
      PRIVATE_IF=eth1
    else
      PUBILC_IF=eth0
      PRIVATE_IF=eth0
    fi
    
    case "$announce_as" in
      public)  ANNOUNCE_IP="`ifconfig $$PUBILC_IF | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\\2/p'`"
               ;;
      private) ANNOUNCE_IP="`ifconfig $$PRIVATE_IF | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\\2/p'`"
               ;;
            *) ANNOUNCE_IP=""
               ;;
    esac
    
    enable -f /usr/lib/sleep.bash sleep
    
    trap "{ curl -L "$$ETCD/v2/keys/skydns/$$DOMAIN" -XDELETE ; exit 0; }" SIGINT SIGTERM
    
    while true; do
      if [[ "$announce_as" == "container" ]]; then
        ANNOUNCE_IP="`docker inspect --format '{{ .NetworkSettings.IPAddress }}' $name`"
      fi
      curl -L "$$ETCD/v2/keys/skydns/$$DOMAIN" -XPUT -d value="{\\"host\\": \\"$$ANNOUNCE_IP\\", \\"port\\": $port}" -d ttl=60 >/dev/null 2>&1
      sleep 45
    done""").substitute(name=self.name, port=self.announce, announce_as=self.announce_as)
    
    def main():
      module = AnsibleModule(
        argument_spec = dict(
            state       = dict(required=True, choices=['present', 'absent', 'enabled', 'disabled']),
            name        = dict(required=True),
            image       = dict(required=True),
            args        = dict(default=None),
            announce    = dict(default=None),
            announce_as = dict(default='private', choices=['public', 'private', 'container']),
            port        = dict(default=None)
        )
      )
    
      state = module.params['state']
      name  = module.params['name']
      image = module.params['image']
      args = module.params['args']
      announce = module.params['announce']
      announce_as = module.params['announce_as']
      port = module.params['port']
      svc = Service(name, image, args, announce, announce_as, port)
    
      if state == 'present':
        module.exit_json(changed=svc.install(module))
    
      if state == 'absent':
        module.exit_json(changed=svc.remove(module))
    
      if state == 'enabled':
        module.exit_json(changed=svc.start(module))
    
      if state == 'disabled':
        module.exit_json(changed=svc.stop(module))
    
      module.fail_json(msg='Unexpected position reached')
      sys.exit(0)
    
    from ansible.module_utils.basic import *
    main()
    


    Давайте посмотрим, что происходит, когда мы запускаем новый сервис; например, запустим influxdb:

    ansible -i hosts node-back-1 -s -m rundock -a 'state=enabled name=influxdb image="registry.s.prod.example.com:5000/influxdb:latest" args="--volumes-from data.influxdb -p $PRIVATE_IP:8083:8083" announce=8086 port=8086'
    

    Ansible добавляет на машину новую задачу для runit, которая содержит две подзадачи, контейнер и анонс:

    $ cat /etc/sv/docker-influxdb/services/service/run
    #!/bin/bash
    
    CONTAINER_NAME=influxdb
    INTERFACE=eth0
    PRIVATE_IP="`ifconfig $INTERFACE | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'`"
    
    docker inspect $CONTAINER_NAME|grep State >/dev/null 2>&1
    if [ $? -eq 0 ]; then
      docker rm $CONTAINER_NAME || { echo "cannot remove container $CONTAINER_NAME"; exit 1; }
    fi
    
    docker pull registry.s.prod.example.com:5000/influxdb:latest
    
    exec docker run -i --rm --name $CONTAINER_NAME --hostname "`hostname`-influxdb" --volumes-from data.influxdb -p $PRIVATE_IP:8083:8083 -p $PRIVATE_IP:8086:8086 registry.s.prod.example.com:5000/influxdb:latest
    

    runit убьет старый контейнер, если он был, скачает новый образ и запустит докер в интерактивном режиме. Если контейнер умрет — runit его перезапустит. В контейнере data.influxdb сделан маппинг на пути в ФС, где influx будет хранить свои данные.

    Второй сервис:

    $ cat /etc/sv/docker-influxdb/services/announce/run
    #!/bin/bash
    
    ETCD="http://192.0.2.1:4001"
    DOMAIN="com/example/prod/s/influxdb/`hostname`"
    INTERFACE=eth0
    
    enable -f /usr/lib/sleep.bash sleep
    
    trap "{ curl -L "$ETCD/v2/keys/skydns/$DOMAIN" -XDELETE ; exit 0; }" SIGINT SIGTERM
    
    while true; do
      PRIVATE_IP="`ifconfig $INTERFACE | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'`"
      curl -L "$ETCD/v2/keys/skydns/$DOMAIN" -XPUT -d value="{\"host\": \"$PRIVATE_IP\", \"port\": 8086}" -d ttl=60 >/dev/null 2>&1
      sleep 45
    

    Модуль для bash добавляет sleep как built-in команду, теперь bash будет обновлять запись для домена, и influxdb будет доступен по node-back-1.influxdb.s.prod.example.com.

    костыль: по-хорошему, анонс надо делать изнутри контейнера, так как анонс будет жив даже если контейнер ушел в crash-loop.

    Теперь прикрутим grafana для фронтенда:

    ansible -i hosts node-back-1 -s -m rundock -a 'state=enabled name=grafana image="tutum/grafana:latest" args="-e INFLUXDB_HOST=influxdb.s.prod.example.com -e INFLUXDB_PORT=8086 -e INFLUXDB_NAME=metrics -e INFLUXDB_USER=metrics -e INFLUXDB_PASS=metrics -e HTTP_PASS=metrics -e INFLUXDB_IS_GRAFANADB=true" announce=8087 port=80'
    

    Тут port и announce разные, так как стандартный контейнер отдает grafana на порту 80, а мы отдаем его наружу на 8087.

    Ну и наконец апстрим в nginx:

    upstream docker_grafana {
        server grafana.s.prod.example.com:8087;
        keepalive 512;
    }
    

    костыль: порты прибиты руками. По-хорошему, что-то вроде этого может научить nginx использовать SRV записи.

    Поговорим о стабильности решения?


    Фронтенд. Если умрет фронтенд, надо обновлять DNS записи. Некоторое время лежим и грустим.

    Обнаружение. etcd/skydns вообще сложно убить, если они адекватно собраны в консенсус.

    Бекенд-сервис. Мы резолвим сервис без имени машины, так что можно запустить несколько бекендов; skydns будет балансировать нагрузку или оперативно подменять умершие сервисы.

    Файловая система. В идеальном мире мы имеем полностью неизменяемое состояние, но в жизни все печальнее. БД, которые понимают репликацию, могут иметь хранилище на локальном диске или в обычном --volume. Там, где надо распределять что-то между контейнерами, работает ceph (paxos, по хорошему, тоже сложно убить).
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 55

      +1
      в mesos и mesosphere это довольно легко решается:

      1) порты мапятся случайным образом
      2) раз в минуту (отдельный тупой скрипт, который узнает где что лежит) пересобирается конфиг для haproxy
      3) снаружи можно всегда обратиться по конкретному адресу и порту к сервисам
        0
        А как в mesosphere решается вопрос локального хранилища, с учетом того, что сервис может быть потенциально запущен где угодно?
          0
          в целом — можно для mesos slave проставить теги, например, на конкретной машине будет только база запускаться. в этом случае диск будет один и тот же. ну и к докеру монтироваться будет конкретная папка.

          а вообще, запускать базу в докере это очень странная идея. только если для тестов или побаловаться
            +2
            почему странная? я нахожу идею запуска всего в докере очень даже хорошей. Я бы CoreOS использовал, если бы не специфика с ядром и сетью.
              0
              шаринг ресурсов как минимум. а так, база это место куда и пишут и читают, так что если она упадет (а месос это подразумевает), то всем будет фигово. обычно в месос и в докерах запускают приложения, в нескольких инстансах. тогда если упадет один контейнер, месос узнает об этом и можно разделить трафик на остальные контейнеры,

              в это же время месос развернет в другом месте новый контейнер заместо упавшего.

              p.s. я тут не рассматриваю вариант master-master, это отдельный разговор

                0
                Я тут себе когда думал тот же постгрес в докер запихать, потом посмотрел на выделенную для докера /8 и подумал что ну его нафиг, пересобирать с новым конфигом каждый раз. Опять же, бекапы. (не, ну конфиг для слейва можно и подбросить, но статические IP для докер контейнеров я не осилил)

                А вот запихать все сайты в шаредхостинге в разных инстансах одного контейнера с подключенными /var/www/$site, указав им в качестве MySQL — конкретный айпишник молотилки SQL — очень даже неплохая идея.
                Вот сейчас я эту идею активно думаю применить на шаредхостинге — один контейнер на всех, много инстансов под каждый сайт с подмонтированными /var/www и логами, и хост система чистая, ну и уязвимостей меньше.
                а снаружи nginx и при старте докер контейнеров обновляется a запись $site.docker.loc.

                опять же, работа в режиме кластера, но это отдельная интересная тема.
                  0
                  пересобирать с новым конфигом каждый раз
                  разные механизмы discovery (в том числе, DNS) это же решают как раз?
                    0
                    возможно, но проще узнать у самого месоса какой адрес у контейнера
                      0
                      ну или так. Я к тому что — в чем проблема запихать постгрес в докер?
                        0
                        то есть вы хотите сказать, что в вашем случае, иметь 2 разных контейнера с базой, в которые пишут сначала в одну, потом в другу — это нормально? я не могу представить кейс, когда запуск второго контейнера не даст простоя всего остального
                          +1
                          master-master да. но для всякого хлама с более допустимым availability резолвится один мастер. Если умрет — слейв промотится в мастер и забирает имя мастера себе.
                            0
                            тогда могу только пожелать удачи, я бы так не рисковал :)
                              +1
                              Я как-то не совсем понимаю при чем тут, собственно, докеризация БД.
                                0
                                К ненужным проблемам, по сути, которых можно избежать, используя sql сервера на мощных отдельных машинах.
                0
                А что не так с ядром и сетью? В CoreOS можно своё кустомное ядро собирать, или это как раз и напрягает?
                  0
                  А есть что почитать? Я так поверхностно понял, что корось может внезапно обновиться и все, что было не в докере, идет лесом.
          0
          В kubernetes на каждой машине висит специальный прокси-сервер (по функциям аналог haproxy), который сам отслеживает изменения конфигурации через etcd и моментально меняет адреса бэкендов, если они поменялись.
            0
            Именно из-за этой фичи я хочу его в скором времени опробовать!
            Вроде маленькая вещь, но kubernetes на её основе (не только конечно) превращает Iaas кластер в Paas по управлению контенерами…
          +2
          Недавно начал использовать docker и возник ряд вопросов.

          Для разработки: почему мы просто не можем установить нужный софт, используя shell-скрипт в vagrant или ansible/chef/puppet? Зачем нужны контейнеры (это ведет за собой потерю простоты и прозрачности использования при разработке).

          Для продакшена: почему просто не ansible/chef/puppet?

          Иногда даже официальные репозитории хаба ломают (сейчас сломан rabbitmq более суток). Нужно держать зеркала на этот случай?
          • UFO just landed and posted this here
              +1
              Установить нужный софт не всегда просто. У одного разработчика стоит gentoo, у другого нестабильная ubuntu, в продакшне стабильный debian. Потом оказывается, что у одного версия библиотеки слишком старая, у другого поменялись пути, у дебиане вообще в дистрибутиве её нет. В случае контейнеров все работают в идентичном окружении.

              Когда используете чужие репозитории, привязывайтесь к конкретной версии. Периодически, когда вы явно этого захотите, можете передвигать версию на более свежую, тестировать всё, и если ок, то коммитить изменения Dockerfile.
                0
                aml
                > Установить нужный софт не всегда просто. У одного разработчика стоит gentoo, у другого нестабильная ubuntu, в продакшне стабильный debian.
                vagrant везде одинаковый. Всем ставим одинаковый начальный образ и инструкцию.
                И все довольны.
                Зачем поверх vagrant ставить docker?

                > Когда используете чужие репозитории, привязывайтесь к конкретной версии.
                Конкретные версии тоже меняются. Например rabbitmq 3.5.0 (конкретная версия) сегодня меняли.

                kostyasha
                Естественно. Docker в этом мешает.
                  +2
                  vagrant везде одинаковый. Всем ставим одинаковый начальный образ и инструкцию.
                  И все довольны.
                  Зачем поверх vagrant ставить docker?

                  Если я вас правильно понял, то вопрос заключается в том мол зачем мне docker, если у меня виртуалка есть созданная vagrant, правильно?
                  В такой постановке вопроса действительно докер скорее всего лишний. Нужно либо одно, либо другое.
                  Докер это другой тип виртуализации — контейнерной, у нее образы занимаю сильно меньше места на диске, в памяти, быстрее стартуют и т.п., если вам это не интересно, то докер вам не нужен конечно.
                    +1
                    Виртуальные машины — это не только очень удобно, но ещё и большие накладные расходы по ресурсам, такие как предвыделенная ОЗУ, например, или заметно медленнее выполнение программы. Docker по производительности близок к железу (в разных тестах потеря варьируется в переделах 0.5-3% производительности). Docker поверх vagrant имеет смысл ставить только для систем, где докер не поддерживается (Windows, Mac OS, старый Linux (спорно, там может и vagrant не взлететь тогда)).
                      –1
                      ReanGD
                      > Докер это другой тип виртуализации — контейнерной
                      при разработке это не реализуемо, ведь у всех разные ОС.

                      frol Я пришел к такому же выводу.
                      Docker для разработчика — зло.
                      Но везде форсируется обратное мнение.
                        +2
                        Вы что-то неправильно прочитали или я ночью плохо что-то написал. Я считаю Docker прекрасным решением! Только, к сожалению, почему-то большинство не понимает как им правильно пользоваться… Я вот сел и недельку вдумчиво изучил ситуацию, зато теперь я представляю что где и как, только теперь я не могу со спокойной душой взять первый попавшийся образ из Docker Hub, так как 99% там хлам, к сожалению.
                          –3
                          > Я считаю Docker прекрасным решением!
                          Для каких конкретно задач?
                          Для разработки не подходит (требует еще одного уровня виртуализации в виде vagrant из-за того, что у разработчиков разные ОС). Значит, для продакшена? А как быть с периодическими проблемами с контейнерами в docker hub? Для продакшена такое недопустимо.

                          ReanGD
                          > Так в том то и дело, что докер изолирует вас от ОС, хоть вы в убунте сидите, хоть в арче — докер всегда работает одинаково.
                          А в OS X или Windows?
                          Ведь как минимум фронтендщикам нужны для работы именно эти ОС.
                          Поэтому нужен еще один слой виртуализации. А если он есть, то зачем тогда docker?
                            +2
                            И для разработки и для продакшена подходит отлично. Только сначала научиться нужно пользоваться.

                            Если ваш юзкейс — деплой на Linux и/или другая ОС, то Docker вам подходит или частично подходит.

                            Рассмотрим случай деплоя только на Linux. Все разработчики хотят они или не хотят, но должны запускать код тоже на Linux, при этом версии либ/пакетов должны совпадать с продакшеном.
                            Ваш вариант с виртуалками:
                            + привычно/удобно
                            + абсолютно точно работает одинаково (медленно) и на Linux хостах и на Windows/Mac OS
                            - весит образ 3-4ГБ(?)
                            - обновление образа — ждём пока весь образ скачается (3-4ГБ) или мучаемся с Puppet/Chef/Ansible/CFEngine (только не говорите, что это ж просто, например, для докера этого делать не нужно (хотя некоторые особо извращённые товарищи зачем-то продолжают это делать))
                            - отъедает какую-то часть ОЗУ прямо на старте (браузер + виртуалка + IDE — для некоторых это может стать «выберите два»)

                            Вариант с докером:
                            + абсолютно точно работает одинаково (быстро) на любом дистре Linux
                            + образ весит (возьмём худший случай с ubuntu образом, хотя я и не люблю его) 200МБ + зависимости приложения + приложение <500МБ
                            + обновление образа — луковичная ФС качает только обновившиеся уровни
                            + ОЗУ потреляет только ваше приложение (на Linux)

                            То есть ваши разработчики должны сам озаботиться любым дистром Linux в виртуалке, в котором будет стоять Docker, например для этого сделали минимальный образ boot2docker. А дальше уже метод разворачивания образа Docker одинаковый и для продакшена, и для разработчиков, у которых уже стоит Linux!

                            С периодическими проблемами Docker Hub нужно бороться приватным Docker Hub — поднимаете на своих серверах и пользуетесь на здоровье. Альтернативно можно вообще в tar-архивы образы заталкивать на CI и так и распространять.
                              0
                              Вы забываете небольшую деталь: сравниваете виртуалку и докер.
                              А надо сравнивать виртуалку и виртуалку с докером.

                              И у второго варианта мы обнаруживаем все минусы первого.
                              Но будут дополнительные минусы:
                              — непривычно/неудобно
                              — требует дополнительных телодвижений на Windows/Mac OS и более глубокого знания Linux.

                              > или мучаемся с Puppet/Chef/Ansible/CFEngine (только не говорите, что это ж просто, например, для докера этого делать не нужно (хотя некоторые особо извращённые товарищи зачем-то продолжают это делать))
                              Или с provision.sh, что не сложнее докера, в котором внутри RUN команд докерфайлов то же самое.
                                +1
                                Не хотите — не используйте. Если у вас ничего на Linux нет — вам Docker не нужен совсем, забудьте, Linux — слишком сложен. Если вы не готовы менять привычки — вам тоже Docker не нужен.

                                P.S. Просто оставлю это здесь: Kitematic a Docker GUI Joins the Docker Family.
                                  0
                                  Больше всего мне нравится, что изначально Линуксовая технология докер, но в GUI нет ни слова о LInux или о хотябы упоминания в Roadmap
                                    0
                                    Потому что для Linux оно не нужно. Kitematic просто автоматизирует связку VirtualBox для запуска boot2docker + Docker.
                                    –1
                                    > Если у вас ничего на Linux нет — вам Docker не нужен совсем, забудьте
                                    Есть конечно же, все рабочее окружение проектов, но не вижу смысла в Docker.
                                    Вы перечислили его плюсы, но они сводятся на нет слоем vagrant под ним.
                                    А с vagrant его уже использовать нет смысла.

                                    > забудьте, Linux — слишком сложен.
                                    А вы знаете, что проекты обычно разрабатываются командами?
                                    И в командах есть не только программисты.
                                    Зачем верстальщику уметь работать с docker, когда ему достаточно базовых навыков работы в командной строке (чтобы команду миграции запустить после апдейта репозитория)?

                                    > Просто оставлю это здесь: Kitematic a Docker GUI Joins the Docker Family.
                                    Зачем? Админ окружение в проекте настраивает (пишет скрипты), и остальные пользуются. Зачем пользователю что-то самому делать?
                                      +2
                                      Это как спорить о apt-get/yum/emerge/pacman VS virtualenv/RMV/bundler. Вы и в продакшен vagrant выкатываете? Или у вас два набора скриптов для деплоя на vagrant и на продакшен?
                                        –1
                                        Два набора.
                                        Потому что продакшен и дев должны быть разными (разные пакеты, разные настройки).
                                        Или желаете видеть на продакшене вместо включенного кеширования отладчик, настроенный для внешних подключений?
                                          +2
                                          То есть ради того чтобы приложению положить разные настройки вы пишите два разных скрипта по разворачиванию вашего проекта. Удобно, ничего не скажешь. «разные пакеты» — а потом у вас всё валится с криком «у меня же работает!»

                                          В общем, я не гуру и вам виднее как поступать со своими проектами. Желаю вам всяческих успехов. Откланиваюсь.
                                  –3
                                  3-4 гига?
                                  Сударь вы либо целенаправленно лжете, либо наивно заблуждаетесь.
                                  Размер образа тот же что у docker.

                                  И что за настройка? Если вам дают уже настроенный образ, или для докера вам дают готовый докер файл или готовый шаблон, а для kvm, xen, virtual box, VMware вам дают только полуфабрикат?

                                  Память, ну да, наверно. 50-100 мегабайт на виртуальную машину это финиш…
                                  А в lxc у вас все контейнеры сделаны грамотно, стартует только один нужный процесс, ну ну. Верю.

                                    +2
                                    Сударь вы либо целенаправленно лжете, либо наивно заблуждаетесь.
                                    Размер образа тот же что у docker.

                                    В докере как минимум оптимизация в том, что дублирующиеся данные не хранятся, т.е. если есть десяток образов на основе убунты, сама убунта будет храниться 1 раз. Ну и плюс, для докера обычно используются минимизированные образы ОС, для чаще виртуалок беруться стандартные.

                                    Память, ну да, наверно. 50-100 мегабайт на виртуальную машину это финиш…
                                    А в lxc у вас все контейнеры сделаны грамотно, стартует только один нужный процесс, ну ну. Верю.

                                    Не знаю как насчет lxc, а в докере за счет того, что каждый старт процесса нужно описывать отдельно — ничего лишнего не стартует
                                      –1
                                      Чаще != всегда
                                      А что докер без lxc уже работать может?

                                      Докер это всего лишь утилита над lxc namespace + diff ы файловой системы

                                      Контейнеры вещь давно известная, и lxc из них не самая опробированная технологи.
                                      Namespace — как бы и без докера сами по себе живут
                                      «Луковичная файловая система» — тоже без докера имею место быть.
                                      Так что же докер такое?
                                        +2
                                        А что докер без lxc уже работать может?

                                        Да может

                                        Контейнеры вещь давно известная, и lxc из них не самая опробированная технологи.
                                        Namespace — как бы и без докера сами по себе живут
                                        «Луковичная файловая система» — тоже без докера имею место быть.
                                        Так что же докер такое?

                                        Что-то вы удалились от обсуждения плюсов/минусов докера.
                                        Да они ничего революционного сами не изобрели, а удачно скомпилировали и распиарили, плюс сделали docker.hub с очень большой базой готовых образов, уменьшили порог входа для использования контейнерной виртуализации.
                                          0
                                          Хм, парни запилили свою прослойку на go.
                                          Молодцы.

                                          «Second, we are introducing a new built-in execution driver which is shipping alongside the LXC driver. This driver is based on libcontainer, a pure Go library which we developed to access the kernel’s container APIs directly, without any other dependencies.»

                                          А теперь по теме.
                                          С тем что ничего нового и революционного мы определились. Ок.

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

                                          Вот и вся мысль

                                          Ну и резюмирую:
                                          Докер это набор утилит по взаимодействию с api ядра linux написанный на go + api
                                          Никаких новых технологий он не несет, но упрощает использование контейнерной виртуализацией для не специалистов, плюс имеет хорошую маркетинговую поддержку.
                              +4
                              при разработке это не реализуемо, ведь у всех разные ОС.

                              Так в том то и дело, что докер изолирует вас от ОС, хоть вы в убунте сидите, хоть в арче — докер всегда работает одинаково.
                      0
                      Уважаемый автор, помоему у Вас просто руки чесались костылей понаписать, вот Вы их и понаписали… Прочитайте про Docker Machine, Docker Swarm, и Docker Compose (да, тот самый fig). Можно начать отсюда.
                        0
                        А вы сами читали статью, на которую ссылаетесь?
                        Docker Machine к теме поста вообще отношения не имеет. Зачем вы предлагаете автору почитать про Compose, если он явно написал, что пользовалься fig'ом и ему он не понравился? Как Swarm решит проблему dns, анонсирования сервисов и балансировки?
                          0
                          Читал, поэтому и дал ссылку.

                          > Docker Machine к теме поста вообще отношения не имеет.
                          Автор использует ansible вместо Docker Machine.

                          > Зачем вы предлагаете автору почитать про Compose, если он явно написал, что пользовалься fig'ом и ему он не понравился?
                          Конечно, не понравился, когда проще на коленке писать полотна bash'a

                          > Как Swarm решит проблему dns, анонсирования сервисов и балансировки?
                          DNS решается Docker Compose, который по links прописывает нужные обновления в контейнеры в /etc/hosts. Анонсировать ничего не надо. Масштабирование — Docker Swarm + Docker Compose scale. Балансировкой заниматься должен балансер (Nginx/HAProxy/...), вот как можно это настроить — www.centurylinklabs.com/auto-loadbalancing-with-fig-haproxy-and-serf/

                          Ещё вопросы?
                            +1
                            Автор использует ansible вместо Docker Machine.

                            То для чего у автора используется ansible вообще не имеет к тому для чего предначначен Docker Machine никакого отношения. Docker Machine предназначен для создания и управления виртуалками (локальными или в облаке) с установленным docker'ом.

                            > Зачем вы предлагаете автору почитать про Compose, если он явно написал, что пользовалься fig'ом и ему он не понравился?
                            Конечно, не понравился, когда проще на коленке писать полотна bash'a

                            По вопросу кастомных наколеночных решений я с вами согласен. Но справедливости ради надо сказать, что swarm и compose были анонсированы совсем недавно и до сих пор они еще не production-ready.

                            DNS решается Docker Compose, который по links прописывает нужные обновления в контейнеры в /etc/hosts. Анонсировать ничего не надо. Масштабирование — Docker Swarm + Docker Compose scale. Балансировкой заниматься должен балансер (Nginx/HAProxy/...), вот как можно это настроить — www.centurylinklabs.com/auto-loadbalancing-with-fig-haproxy-and-serf/

                            Ну как же анонсировать ничего не надо, если по вашей же ссылке используется serf для service discovery?
                              0
                              То для чего у автора используется ansible вообще не имеет к тому для чего предначначен Docker Machine никакого отношения. Docker Machine предназначен для создания и управления виртуалками (локальными или в облаке) с установленным docker'ом.
                              Виноват, снова Docker Swarm с Docker Machine спутал, действительно Docker Machine в конкретном топике ни при делах. В общем, суть в том, что если поднять на своих серверах Docker Swarm, то не нужно заходить по ssh (ansible) на эти машины и делать docker run.

                              По вопросу кастомных наколеночных решений я с вами согласен. Но справедливости ради надо сказать, что swarm и compose были анонсированы совсем недавно и до сих пор они еще не production-ready.
                              Да, Swarm совсем свежий, но Compose (fig) развивается давно. Тут сложно сказать что менее production ready — наколеночный костыльный скрипт или специализованное решение. Но выбор за каждым. Просто иногда лучше не советовать ничего, чем советовать вредные подходы.

                              Ну как же анонсировать ничего не надо, если по вашей же ссылке используется serf для service discovery?
                              Тут я опять неверно выразился. В случае реализации того решения, что у автора, без динамического масштабирования, анонсировать ничего не надо. А в красивом подходе, конечно прийдётся что-то в духе Serf использовать.
                              +1
                              Ещё вопросы?

                              Используете линки в работе? Как решаете проблему рестарта линкованого контейнера? Рестартуете всю цепочку?
                              Как делаете двусторонние линки?
                                +1
                                Да, использую линки. А какая проблема? Вот набросал docker-compose.yml (нужен bash и nc, под рукой был ubuntu образ, можете любой другой использовать с указанными утилитами):

                                qq1:
                                    image: ubuntu
                                    links:
                                        - qq2
                                    command: "bash -c 'for i in `seq 1000`; do echo $i | nc qq2 10000 ; sleep 1; done'"
                                
                                qq2:
                                    image: ubuntu
                                    command: "bash -c \"while true; do bash -c 'for i in `seq 1000`; do echo REPLY $(getent hosts `hostname`) ; sleep 0.5; done' | nc -l -p 10000 0.0.0.0 ; done\""
                                


                                qq2 слушает входящие соединения, выводит, что там пришло и в ответ кидает REPLY со своим IP+именем.

                                Запуск:
                                $ docker-compose up
                                


                                В соседней консоли:
                                $ docker-compose restart qq2
                                


                                Вывод на первой консоли:
                                $ sudo docker-compose up
                                Recreating tmp_qq2_1...
                                Recreating tmp_qq1_1...
                                Attaching to tmp_qq2_1, tmp_qq1_1
                                qq2_1 | 1
                                qq1_1 | REPLY 172.17.1.2 de8138e439ae
                                qq1_1 | REPLY 172.17.1.2 de8138e439ae
                                ...
                                ...
                                qq2_1 | 11
                                qq1_1 | REPLY 172.17.1.5 de8138e439ae
                                qq1_1 | REPLY 172.17.1.5 de8138e439ae
                                


                                Двусторонних линков пока не приходилось делать, можно пример, пожалуйста?
                                  0
                                  Двусторонние линки — два сервиса должны знать друг о друге. Получается проблема курицы и яйца.

                                  По вашему примеру. Он у меня не заработал и я его немного переделал.

                                  $ cat docker-compose.yml 
                                  qq1:
                                      image: ubuntu
                                      links:
                                          - qq2
                                      command: "bash -c 'while true; do data=$RANDOM; echo REQUEST: $data; nc qq2 10000 <<< $data; sleep 1; done'"
                                  
                                  qq2:
                                      image: ubuntu
                                      command: "bash -c 'while true; do echo RESPONSE: $(nc -l 10000); sleep 0.5; done'"
                                  


                                  $ docker-compose up
                                  Recreating compose_qq2_1...
                                  Recreating compose_qq1_1...
                                  Attaching to compose_qq2_1, compose_qq1_1
                                  qq2_1 | RESPONSE: 24896
                                  qq1_1 | REQUEST: 24896
                                  qq1_1 | REQUEST: 16226
                                  qq2_1 | RESPONSE: 16226
                                  qq1_1 | REQUEST: 23182
                                  qq2_1 | RESPONSE: 23182
                                  qq1_1 | REQUEST: 10674
                                  qq2_1 | RESPONSE: 10674
                                  qq1_1 | REQUEST: 7200
                                  qq2_1 | RESPONSE: 7200
                                  qq1_1 | REQUEST: 30534
                                  qq2_1 | RESPONSE: 30534
                                  ... тут происходит docker-compose restart qq2 ...
                                  qq1_1 | REQUEST: 7482
                                  qq1_1 | REQUEST: 30388
                                  qq1_1 | REQUEST: 21044
                                  qq1_1 | REQUEST: 4143
                                  qq1_1 | REQUEST: 11100
                                  qq1_1 | REQUEST: 21941
                                  qq1_1 | REQUEST: 5191
                                  qq1_1 | REQUEST: 27923
                                  
                                    +1
                                    Я знаком с понятием двусторонний. Приведите пример такого проекта!

                                    По вашему коду:
                                    Всё работает, просто вы вывод больше не видите от qq2 в этой консоли, если бы не работало, вы бы получали timeout или порт закрыт (я сейчас с телефона и не могу проверить что именно будет видно в консоли). Я response посылал обратно в nc от qq2 — поэтому у меня и видно вывод.

                                    P.S. «он у меня не заработал» — ей богу вам тестировщики тоже баги репортят «у нас что-то сломалась»? Как именно не заработал?
                                      0
                                      P.S. «он у меня не заработал» — ей богу вам тестировщики тоже баги репортят «у нас что-то сломалась»? Как именно не заработал?

                                      Получаю такой вывод:

                                      $ docker-compose up
                                      Recreating compose_qq2_1...
                                      Recreating compose_qq1_1...
                                      Attaching to compose_qq2_1, compose_qq1_1
                                      qq2_1 | 1
                                      qq2_1 | 14
                                      qq2_1 | 27
                                      qq2_1 | 40
                                      qq2_1 | 53
                                      qq2_1 | 66
                                      qq2_1 | 78
                                      qq2_1 | 91
                                      qq2_1 | 104
                                      

                                      Не увидел никакого REPLY 172.17.1.5 de8138e439ae.

                                      Всё работает, просто вы вывод больше не видите от qq2 в этой консоли

                                      Ваша правда. Все работает и это круто!

                                      Я знаком с понятием двусторонний. Приведите пример такого проекта!

                                      Хочу чтоб 2 веб-сервиса могли дергать друг друга за апи. Думал, может подскажете какой-то вариант.

                                      А как вы рулите линками между хостами? Например 2 веб-сервера, на разных хостах и БД на отдельном. Как этим красиво рулить?

                                      P.S. Большое спасибо.
                                        +1
                                        Очень подозрительно, что это там с docker-compose. У меня версия 1.2.0rc1, а у вас?

                                        На счёт двухстороннего взаимодействия, я вижу два разных пути решения:
                                        1. на уровне приложений — один дёргает другого и говорит куда тому коннектиться в следующий раз. Такое решение не очень красивое, но в зависимости что там за сервисы — это может иметь смысл.
                                        2. делать третью сторону, регистратор обоих приложений (я пока не имею большого опыта в этой области, но начать можно с таких проектов: registrator, serf, consul, skydns)

                                        На счёт нескольких хостов — Docker Swarm + Docker Compose скрывают подробности того, где именно запущен образ и links работает прозрачно. Однако, это в теории, я БД в Swarm ещё не запихивал, она у меня просто на одном сервере запущена через Compose.
                          0
                          дел

                          Only users with full accounts can post comments. Log in, please.