如何持续构建版本化的 Docker 镜像

如何持续构建版本化的 Docker 镜像

我在 GitHub 上有一个 PHP 项目,我想为其构建版本化的 Docker 镜像。有点像 CoreOS Container Linux 的 alpha、beta 和稳定发布渠道。

对于开发,我遵循 Git Flow 原则并使用功能 -> 开发 -> 主分支。

我已经连接了 Travis 来进行自动化 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 所做的事情的最佳方法是什么?

相关内容