Сюрпризы chef-a или история одного расследования

    imageНе так давно мы в компании Acronis перешли на провиженинг части наших виртуальных машин на Chef. Ничего особенно нового: все виртуальные машины создаются посредством ESXI, а центральный chef-server раздает им свои рецепты, тем самым автоматически поднимая на них окружение, исходя из их ролей. Такая система работала без проблем и сбоев довольно долго. Она освободила нас от большого количества ручной работы, постоянного контроля за окружением машин и необходимостью помнить какое ПО и настройки на них стоят, ведь достаточно открыть веб-консоль chef-server-а, выбрать нужную нам ноду и увидеть все ее роли и настройки.

    Все было отлично до тех пор, пока нам не поставили задачу перенести один сайт с внешнего хостинга к нам на сервера, что в итоге привело к охоте на баги и расследованиям в стиле Скуби Ду.

    Если заинтересовались, добро пожаловать под кат.

    Сначала я, как всегда, попросил нашу IT службу поднять на ESXI пустую Debian виртуалку. Стандартные настройки, ничего лишнего. Такое уже делалось не раз и не два, и процесс был отлажен, поэтому никакого подвоха я не ожидал. Получив на руки IP и креды для доступа, я выполнил стандартную команду knife bootstrap, чтобы установить на нее chef-client и подключить к центральному chef-server-у. Все прошло без проблем, и я полез в веб-интерфейс, чтобы все установить необходимые роли и прописать атрибуты для новой ноды.

    Здесь надо отметить, что при установке, chef пробегается по всей виртуалке и собирает все необходимые данные о системе: параметры железа, настройки ОС итд. После чего отправляет их на chef-server, где их можно увидеть в настройках ноды. Обычно этих данных довольно много, и я редко когда в них заглядываю, но в этот раз мое внимание привлек тот факт, что у этой новой ноды их как-то очень мало. Сравнив ее атрибуты с атрибутами других машин я еще раз в этом убедился. Но самое странное было то, что последний отображаемый атрибут новой ноды назывался gce и отсутствовал у остальных машин. Кроме того, если его развернуть, отображалось пустое ничего.

    image

    Т.к. виртуалку планировалась отправить напрямую в продакшн, меня такая ситуация не устраивала, и я решил раскопать, что же с ней не так. Первым делом я заглянул в консоль браузера и увидел там странную ошибку о том, что браузер отказывается загрузить iframe с www2.searchnut.com/?subid=704003&domain= на странице с https (веб-консоль chef-server открывается только по https). Загуглив этот адрес, я увидел кучу результатов, рассказывающих, что это разновидность malware и даже предлагающих варианты ее удалить. Заглянув в исходный код страницы, я увидел, что в том месте, где обычно выводится значение атрибута, вывелось штук 6 одинаковых кусков html-кода:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
      <head>
        <title>metadata.google.internal [6]</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="pragma" content="no-cache">
        <style>
        body { margin:0; padding:0; }
        </style>
      </head>
      <body scroll="no">
        <div style="position:absolute; top:0px; left:0px; width:100%; height:100%;">
        <div id="plBanner"><script id="parklogic" type="text/javascript" src="http://parking.parklogic.com/page/enhance.js?pcId=5&domain="></script></div>
          <iframe src="http://www2.searchnut.com/?subid=704003&domain=" style="height:100%; width:100%; border:none;" frameborder="0" scrolling="yes" id="park" />
        </div>
      </body>
    </html>
    

    На тот момент вариантов было не много. Я рашил, что либо виртуалку успели поломать, либо поломали сам chef и внедрили в пакет какую-то дрянь. В любом случае, ничего хорошего ожидать не приходилось, и я решил писать напрямую в support chef-а. Описав проблему и отправив тикет, я параллельно развернул еще одну виртуалку у себя на машине, и провел над ней те же действия. Какого же было мое удивление, когда я увидел, что никакого gce на ней нет! Она работала без проблем, как и все остальные.

    Пока ждал ответа от саппорта, я решил поискать, что же может значить аббревиатура gce. Оказалось, что за GCE стоял Google Compute Engine, то есть клауд от Гугла, который позволял хостить у себя виртуалки, и chef может их провижинить.

    К этому моменту мне ответили из саппорта и предложили выполнить команду ohai -l debug > ohai.log 2>&1, чтобы увидеть, как этот атрибут вообще появляется. Важно отметить, что Ohai — часть системы chef, которая как раз и собирает всю информацию о системе. В логах я увидел следующее:

    [2014-10-03T08:27:30-04:00] DEBUG: has_ec2_mac? == false
    [2014-10-03T08:27:30-04:00] DEBUG: looks_like_ec2? == false
        [2014-10-03T08:27:30-04:00] DEBUG: can_metadata_connect? == true
        [2014-10-03T08:27:30-04:00] DEBUG: looks_like_gce? == true
    [2014-10-03T08:27:31-04:00] DEBUG: has_euca_mac? == false
    [2014-10-03T08:27:31-04:00] DEBUG: has_euca_mac? == false
    [2014-10-03T08:27:31-04:00] DEBUG: has_euca_mac? == false
    [2014-10-03T08:27:31-04:00] DEBUG: looks_like_euca? == false
    

    Самое важное я выделил отступом. По какой-то причине, Ohai решил, что эта виртуалка хостится в Google Cloud, хотя это было не так. Кроме того, меня привлекла строка can_metadata_connect, которая на всех других виртуалках показывала false. Задав резонный вопрос в IT, я получил вполне резонный ответ — это обычная ESXI виртуалка на нашем родном сервере, ни о каком GCE и речи идти не может.

    Chef, как и Ohai — opensource продукты и их исходиники можно найти в github, куда я и направился. Поискав в исходниках Ohai по строке looks_like_gce, я наткнулся на интересный кусок кода в файле gce_metadata.rb:

    GCE_METADATA_ADDR = "metadata.google.internal" unless defined?(GCE_METADATA_ADDR)
    GCE_METADATA_URL = "/computeMetadata/v1beta1/?recursive=true" unless defined?(GCE_METADATA_URL)
    
    def can_metadata_connect?(addr, port, timeout=2)
      t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
      saddr = Socket.pack_sockaddr_in(port, addr)
      connected = false
    
      begin
        t.connect_nonblock(saddr)
      rescue Errno::EINPROGRESS
        r,w,e = IO::select(nil,[t],nil,timeout)
        if !w.nil?
          connected = true
        else
          begin
            t.connect_nonblock(saddr)
          rescue Errno::EISCONN
            t.close
            connected = true
          rescue SystemCallError
          end
        end
      rescue SystemCallError
      end
      Ohai::Log.debug("can_metadata_connect? == #{connected}")
      connected
    end
    

    Из него следовало, что если Ohai может с виртуалки запросить ресурс metadata.google.internal, и получить ответ, то машина автоматически считается GCE. Поискав по строке metadata.google.internal в гугл, я нашел, что это адрес внутреннего API Google Cloud, который доступен только из его сети, а, соответственно, получить доступ к нему со своей ноды я никак не мог.

    Проверив это убеждение на старых виртуалках, я увидел:

    $ wget http://metadata.google.internal
    --2014-10-04 13:47:29--  http://metadata.google.internal/
    Resolving metadata.google.internal... failed: nodename nor servname provided, or not known.
    wget: unable to resolve host address ‘metadata.google.internal’
    

    Но с этой новой виртуалки запрос проходил без проблем:

    $ wget http://metadata.google.internal.com
    --2014-10-04 13:50:38--  http://metadata.google.internal.com/
    Resolving metadata.google.internal.com... 74.200.250.131
    Connecting to metadata.google.internal.com|74.200.250.131|:80... connected.
    HTTP request sent, awaiting response... 302 Found
    Location: http://ww2.internal.com [following]
    --2014-10-04 13:50:39--  http://ww2.internal.com/
    Resolving ww2.internal.com... 208.73.211.246, 208.73.211.166, 208.73.211.232, ...
    Connecting to ww2.internal.com|208.73.211.246|:80... connected.
    HTTP request sent, awaiting response... 200 (OK)
    Length: 1372 (1.3K) [text/html]
    Saving to: ‘index.html’
    
    100%[==============================================>] 1,372       --.-K/s   in 0s
    
    2014-10-04 13:50:40 (28.4 MB/s) - ‘index.html’ saved [1372/1372]
    

    Заглянув в содержание index.html, я увидел тот самый злополучный HTML код. Но как это могло быть? Ведь все виртуалки абсолютно одинаковые, используют одинаковый DNS 8.8.8.8. И что за ww2.internal.com, на который идет запрос? Запустив nslookup я увидел:

    $ nslookup metadata.google.internal
    Server:     8.8.8.8
    Address:    8.8.8.8#53
    
    Non-authoritative answer:
    metadata.google.internal.com    canonical name = internal.com.
    Name:   internal.com
    Address: 74.200.250.131
    

    metadata.google.internal почему-то резолвился в metadata.google.internal.com, и в этом была вся беда. Быстро заглянув в /etc/resolv.conf, я увидел строчку search com, которой на остальных машинах отродясь не было.

    Ответ оказался прост. При создании этой виртуалки, ей было дано название new-site.com. Инсталлер операционки подхватывал это название, отделял com и добавлял в resolv.conf автоматически.

    Таким образом, когда Ohai пробегался по системе и делал запрос к metadata.google.internal, то получал ответ с metadata.google.internal.com, и думал, что он на GCE машине. После чего делал запросы к GCE API, получая в ответ лишь эти странички с iframe-ом.

    Выходит, что каждый, кто назовет свою виртуалку что-нибудь.com автоматически получит такую проблему. Естественно, я отписался в support chef-а, где меня заверили, что отправят тикет напрямую ребятам, которые пишут этот Ohai. Так что, надеюсь, скоро эту проблему поправят.

    На этом расследование подходит к концу. Виновные наказаны, хорошие опять победили. Спасибо что были с нами!
    Acronis 87,61
    Компания
    Поделиться публикацией
    Комментарии 5
    • 0
      Странно, что вы первый на это наступили.
      В зарубежном пространстве больше шансов на это напороться.
      Может быть, новая версия ohai?
      • 0
        Не думаю, что версия ohai новая. Судя по логам на гитхабе, файл довольно старый, а метод проверки на GCE был написан 2013 году.
        • 0
          Да нет, просто у зарубежных коллег принято давать машинам вменяемый hostname.
        • +1
          Вы меня опередили. На своем доткоме успел напороться, но расследование не успел завершить. А теперь все и так понятно. Спасибо.
          • +2
            Игра OHAI в угадайку уже не малому количеству народа проела мозг.
            Например OHAI по умолчанию не может определить что он в EC2 если запустить сервер в VPC и у каждого кто на это натолкнется, дебаг занимает приличное количество времени что бы узнать про хинты.

            Так вот — что бы избежать таких проблем, с недавних пор, можно подсказывать OHAI на какой виртуалке он работает.
            Для этого надо создать пустой фаил типа

            touch /etc/chef/ohai/hints/ec2.json

            К сожалению, я незнаю существует ли хинт _not_a_cloud_, если не найдете хинта для себя — попросите у шефа.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое