§ Кеширующий прокси для репозиториев.

У меня давно стояла задача сделать локальный репозиторий для Fedora, особенно старых версий, с тем что бы не тягать каждый раз кучу пакетов из интернетов, а преспокойненько брать их с локального хоста. Тащить к себе весь репозиторий федоры нерационально, ибо вес его переваливает за 4 десятка гигабайт, а реально используемых пакетов в нем всего сотни на полторы мегабайт. Вытащить нужные версии пакетов и создать свой репозиторий при помощи createrepo возможно, но в этом случае если ктото из разработчиков собирает пакет с новыми зависимостями - все ломается, и пакеты приходится докачивать в репозиторий рукам, пересобирая репозиторий заново.

Распространенным решением является запуск обычного кеширующего сервера на базе squid, с тонкой настройкой кеша, но squid хранит свой кеш в таком виде что на него печально смотреть. Если кеш повредится - то придется его сбросить и все сначала. Что делать? Готовых подходящих решений я не нашел, поэтому решил написать свой велосипед, взяв за основу скрипт реализующий простейший прокси на Ruby из интернетов, и тщательно допилив его напильником.

Выбор языка был прост - это Ruby. Почему? Ну наверное под влиянием статьи о том отчего админы не умеют программировать. Хорошая статья в которой автор не без обоснования призывает придерживаться одного единственного языка программирования для всех скриптов. Я изначально выбрал ruby, поэтому пишу на нем абсолютно все.

Для начала нам потребуется установить сам ruby и кое что ещё

# aptitude install ruby ruby-dev rubgem curl

Кроме руби, я установил ещё и curl, это связано больше с тем что можно конечно использовать libcurl, но профит от его использования не сильно больше, а использование отдельного gem для curl добавит геммороя. Итак, ruby стоит rubygem стоит, ставим недостающий gem

# gem install logging

Все, а теперь собственно наш скрипт:

#!/usr/bin/ruby

require 'socket'
require 'uri'
require 'logger'

base_path = "/tmp/cache"
logfile = "/tmp/repo-cacher.log"
port = 8080

# Logging
@log = Logger.new(logfile,0)
@log.formatter = proc {|severity, datetime, progname, msg| "#{`date +"%Y-%m-%d %H:%M:%S"`.chomp} #{severity}: #{msg}\n"}

# Start the caching proxy
begin
  # Open TCP Port for listening requests
  @socket = TCPServer.new(port)
  puts "Server started on port #{port}"
  @log.info "Server started on port #{port}"

  # Open every request in dedicated thread
  loop do
    to_client = @socket.accept
    Thread.new do
      request_line = to_client.readline
      url     = request_line[/^\w+\s+(\S+)/, 1]
      filename = base_path+URI::parse(url).path

      if !File.exists?(filename)
        @log.info "Downloading file #{filename}"
        # Save file from server to the local filesystem for caching
        `mkdir -p #{File.dirname(filename)}` if !File.exists?(File.dirname(filename))
        `curl -s -k #{url} --output #{filename}`
      end

      # Sending file
      @log.info "Sending #{filename} to client"
      buff = ""
      to_file = File.open(filename)

      loop do
        to_file.read(4048, buff)
        to_client.write(buff)
        break if buff.size < 4048
      end

      # Close all
      to_client.close
      to_file.close
      @log.info "Transfer finnished."
    end
  end
rescue Interrupt
    puts 'Got Interrupt..'
# Ensure that we release the socket on errors
ensure
  if @socket
    @socket.close
    puts 'Socked closed..'
  end
  puts 'Quitting.'
  @log.info "Quitting..."
end

Вот и все. Дальше даем права на выполнение, и можно запускать с помощью стандартного start-stop-daemon

start-stop-daemon -Sbmp /var/run/repo-cacher.pid /opt/repo-cacher/repo-cacher.rb

Теперь в файле /etc/yum.repos.d/fedora.repo можно добавить строчку 

proxy=http://your-server:8080/

И спокойно запускать установку файлов из стандартного репозитория. Скрипт сам посмотрит сохраненную копию у себя в /repo/cache, и если файла нет - то скачает его, положит куда нужно, и передаст клиенту. Если сохраненная копия есть в наличии - то сразу отдаст ее.

Есть три важных момента:

1. Скрипт практически не обрабатывает эксепшны, соответственно не очень безопасен. Я писал его для роботов, и ограничен он по портам и хостам.

2. Имя файла жестко связоно с путем, по которому он расположен. Тоесть /pub/linux/fedora/file.rpm и /linux/fedora/file.rpm - это разные файлы, поэтому необходимо проследить что все репозитории которые вы используете в работе имеют одинаковую структуру файлов. У меня они раздаются chef'ом централизовано, так что приключения исключены.

3. Перед тем как отдать файл клиенту, сервер его сперва скачает сам. Если файл 10-20 метров, то при нашей скорости интернетов прокси успевает скачать файл до таймаута клиента, но если файл будет действительно большой или интернет канал слабый, это может привести к отключению клиента по timeout. В этом случае скорее всего придется повторить попытку чуть познее, когда прокси докачает тот самый файл, и сможет раздать его из локального хранилища.

Вот собственно и все. Желающие могут самостоятельно забахать скрипт автозапуска и почивать на лаврах.


comments powered by Disqus