我在 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 所做的事情的最佳方法是什么?