Назад | Перейти на главную страницу

Как непрерывно создавать версионные образы Docker

У меня есть проект PHP на GitHub, для которого я хотел бы создавать образы Docker с поддержкой версий. Вроде как CoreOS Container Linux с их альфа-, бета- и стабильными каналами выпуска.

Для разработки я следую принципу Git Flow и использую функцию -> develop -> master ветвление.

Я подключил Трэвиса для автоматического тестирования PHPUnit и добавил CircleCI сегодня, так как нашел Rakefile, который может автоматически генерировать версию. Которая используется как тег.

Мой текущий .travis.yml:

---
services:
  - docker
sudo: required
env:
  global:
    - ...

addons:
  jwt:
    secure: ...
cache:
  directories:
    - /home/travis/docker/
before_install:
  - "docker --version"
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then echo "ENV GIT_SHA ${TRAVIS_COMMIT::8}" >> Dockerfile; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]] && [[ -f ${DOCKER_CACHE_FILE} ]]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load; fi'
before_script:
  - "env > .env"
install:
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker build -t ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} --pull=true .; fi'
language: php
notifications:
  slack:
    secure: ...
php:
  - "5.6"
script:
  - "phpunit --bootstrap tests/bootstrap.php --testdox tests --coverage-text"
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then mkdir -p $(dirname ${DOCKER_CACHE_FILE}) ; docker save $(docker history -q ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} | grep -v "<missing>") | gzip > ${DOCKER_CACHE_FILE}; fi'
after_success:
  - 'if [[ $TRAVIS_PHP_VERSION == "5.6" ]] && [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then sh generate-api.sh; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${DOCKER_REPOSITORY}:travis-${TRAVIS_BUILD_NUMBER}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${AWS_ACCOUNT_NUMBER}.dkr.ecr.eu-west-1.amazonaws.com/${ECR_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker push ${DOCKER_REPOSITORY}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then pip install --user awscli; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then export PATH=$PATH:$HOME/.local/bin; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then eval $(aws ecr get-login --region eu-west-1); fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker push ${AWS_ACCOUNT_NUMBER}.dkr.ecr.eu-west-1.amazonaws.com/${ECR_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'

И мой текущий circle.yml:

---
machine:
  services:
    - docker
  php:
    version: 5.6.22

dependencies:
  override:
    - docker info
    - gem install httparty
    - rake build

test:
  override:
    - mkdir -p $CIRCLE_TEST_REPORTS/phpunit
    - phpunit --bootstrap tests/bootstrap.php --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml tests
    - docker run -d -p 9000:9000 storecore/php:latest; sleep 10
    - nc -z -w5 localhost 9000

И используемый Rakefile:

require 'rake'
require 'httparty'
require 'json'

# Read the base version from VERSION file.
def version
  file = File.readlines('./version.php')
  v = file[1].scan(/'([^']*)'/)
  v[1].join(",")
end

# The name of the container
def container_name
  'php'
end

# The username the container is pushed to on DockerHub
def username
  'storecore'
end

# Get the latest version for the given base version provided by #version
def hub_version
  base = version
  taginfo = JSON.parse(HTTParty.get("https://hub.docker.com/v2/repositories/#{username}/#{container_name}/tags/").body)['results']

  return { base: base, build: nil } if taginfo.nil?
  tags = []
  taginfo.each do |tag|
    tags << tag['name']
  end
  current_base = tags.grep(/#{base}/)
  return { base: base, build: nil } if current_base.empty?
  build = current_base.sort { |x, y|
    a = x.split('.')[base.split('.').count].to_i
    b = y.split('.')[base.split('.').count].to_i
    a <=> b
  }.last.split('.').last.to_i
  { base: base, build: build }
end

# return current hub version for the current base
def latest_hub_version
  latest = hub_version
  "#{latest[:base]}.#{latest[:build]}"
end

# return the next version for the current base
def next_version
  latest = hub_version
  base = version
  build = latest[:build] || -1
  build += 1
  "#{base}.#{build}"
end

task :install_deps do
  sh 'gem install bundler'
  sh 'bundle install'
end

desc 'login into Docker Hub'
task :login do
  sh "docker login -u #{ENV['DOCKER_USER']} -p #{ENV['DOCKER_PASS']}"
end

desc 'tags latest as next_version'
task :tag => :login do
  sh "docker tag #{username}/#{container_name}:latest #{username}/#{container_name}:#{next_version}"
end

desc 'pushes the next_version and latest to docker hub'
task :push => :tag do
  sh "docker push #{username}/#{container_name}:#{next_version}"
  sh "docker push #{username}/#{container_name}:latest"
end

desc 'builds as latest'
task :build => :install_deps do
  sh "docker build --rm=false -t #{username}/#{container_name}:latest ."
end

task default: [:build, :push]

Но, в конце концов, оба решения не предоставляют мне образ Docker для каждого канала выпуска и по-своему ограничены. Мне это кажется хакерским. Бьюсь об заклад, есть лучшие подходы или инструменты, которые я мог бы использовать. Я не против полностью переключиться на что-то другое.

Что было бы лучшим подходом для достижения чего-то вроде того, что делает CoreOS?