gcr.io 国内源 —— 基于 Travis CI + GitHub + DockerHub + Google Cloud 实现自动定时同步 gcr.io 镜像到 DockerHub

一、背景介绍

        由于国内网络原因,gcr.io 仓库里的镜像是无法直接拉取到的,这给开发工作造成了极大的不便

        本文介绍一种方法能够实现自动化地定期地将 gcr.io 仓库中的镜像同步到个人 DockerHub 账户

        实现该方案需要满足以下条件

        a)  已注册 GitHub 账号 https://github.com/

        b)  已注册 DockerHub 账号 https://hub.docker.com

        c)  已注册 Google Cloud 账号 (需要 Fan Qiang)https://cloud.google.com/

        d)  已注册 Travis CI 账号 (可以使用 GitHub 账号登录)https://www.travis-ci.org/

二、实现步骤

2.1  创建一个 Google Cloud 服务账户,以读取 gcr.io 仓库中的镜像列表

        首先,登录 Google Cloud 控制台,点击菜单 “IAM和管理” -> “服务账号”

       (该功能需要在某个项目下操作,如果还没有项目,可以点 [创建] 新建一个项目,我的项目为 gcr-scan)

        

        填入 “账号名称” 和 “账号ID”,点击 “创建”,之后授予权限,角色选择 “容器分析备注查看者”,点击 “继续”

        

        点击 “创建密钥”

        

        选择 json 格式,点击 “创建”,会提示下载该 json 文件,把它命名为 gcloud.config.json ,保存起来备用

        该文件是实现读取 gcr.io 仓库中镜像列表的关键

        

 2.2  获取 DockerHub 登录密钥文件

        找一台安装有 Docker 的机器,执行 `docker login` 命令

        输入你的 DockerHub 用户名和密码,提示 Login Succeeded 即登录成功

        

        密钥文件会自动生成在 ~/.docker/config.json,将该 config.json 文件保存备用

2.3  创建 SSH 密钥,并在 GitHub 上进行授权

        首先在本地生成一个 SSH 密钥

# ssh-keygen -t RSA -P ""

         

         生成的私钥文件位于 ~/.ssh/id_rsa,将该文件保存备用

         另外需要将 ~/.ssh/id_rsa.pub 公钥内容配置到 GitHub

         登录 GitHub,进入 “Settings” 菜单,点击 “New SSH key”

         

         

         填入你的 SSH 公钥信息,点击 “Add SSH key”

        

2.4  创建 GitHub 项目,配置 Travis CI 策略

        创建的 GitHub 项目名称任意,可以参考我的 https://github.com/SataQiu/gcr_sync

        然后克隆项目到本地:git clone https://github.com/<你的GitHub账户名>/<你起的项目名称>

        首先,创建一个 .travis.yml 文件,放置到项目根目录,该文件告诉 travis ci 如何构建该项目

        然后,将 2.1、 2.2、 2.3 步分别生成的 gcloud.config.json、config.json、id_rsa 放置到项目根目录

        将三个文件打包到一起,执行 :

# tar czvf config.tar.gz gcloud.config.json config.json id_rsa

        紧接着,我们需要对 config.tar.gz 进行加密,该过程需要 travis 命令行工具,为了省去安装 travis 的麻烦

        我们通过容器来执行文件加密,假设你的项目路径位于 /home/shida/src/gcr_sync

# docker run -it -v /home/shida/src/gcr_sync:/root shidaqiu/travis-cli

         在容器里,登录 travis

# travis login

         输入 Travis CI 账号密码(其实就是 GitHub 账号密码)

         

         登录成功后,执行加密命令

# travis encrypt-file conf.tar.gz --add

          如果这里加密不成功,就需要登录 Travis CI 先关联上 GitHub 项目,点击 “Sync Accont”

          

          该加密命令会生成一个 conf.tar.gz.enc 加密文件,同时修改 .travis.yml 文件

          加密完成,退出容器即可

          之后,在项目根目录创建一个文件夹,命名为 .travis

          将 conf.tar.gz.enc 文件移动到该文件夹,并从项目删除加密前的原文件

# rm -f conf.tar.gz gcloud.config.json config.json id_rsa

         修改 .travis.yml

         注意 openssl aes-256-cbc 这句不要照抄我的,要保持加密完文件后该文件里面默认的值

         当执行完加密文件操作,openssl aes-256-cbc 这句会自动生成到 .travis.yml 里

sudo: required
language: python
python:
- '2.7'
addons:
  apt:
    packages:
    - docker-ce
branches:
  only:
  - master
install:
- git remote -v
script:
- "bash  ./sync.sh 10"
before_install:
- export start_time=$(date +%s)
- mkdir -p ~/.docker
- mkdir -p ~/.ssh 
- openssl aes-256-cbc -K $encrypted_0a93db31c783_key -iv $encrypted_0a93db31c783_iv
  -in .travis/conf.tar.gz.enc -out ~/conf.tar.gz -d
- tar zxf ~/conf.tar.gz -C ~
- mv ~/config.json ~/.docker/
- mv ~/id_rsa ~/.ssh/
- chmod 600 ~/.ssh/id_rsa
- chmod 600 ~/.docker/config.json
- eval $(ssh-agent)
- ssh-add ~/.ssh/id_rsa
- df -h

         然后,在根目录创建 sync.sh 脚本

         注意修改 MY_REPO 为你的 DockerHub 账户名,以及对应修改 git_init() 函数中的账户信息为你自己的

#!/bin/bash
max_process=$1
MY_REPO=shidaqiu
interval=.
max_per=70
google_list=repo/google
#--------------------------

Multi_process_init() {
    trap 'exec 5>&-;exec 5<&-;exit 0' 2
    pipe=`mktemp -u tmp.XXXX`
    mkfifo $pipe
    exec 5<>$pipe
    rm -f $pipe
    seq $1 >&5
}

git_init(){
    git config --global user.name "SataQiu"
    git config --global user.email xxx@qq.com
    git remote rm origin
    git remote add origin git@github.com:SataQiu/gcr_sync.git
    git pull
    if git branch -a |grep 'origin/develop' &> /dev/null ;then
        git checkout develop
        git pull origin develop
        git branch --set-upstream-to=origin/develop develop
    else
        git checkout -b develop
        git pull origin develop
    fi
}

git_commit(){
     local COMMIT_FILES_COUNT=$(git status -s|wc -l)
     local TODAY=$(date +%F)
     if [[ $COMMIT_FILES_COUNT -ne 0 && $(( (`date +%s` - start_time)/60 ))  -gt 45 ]];then
        git add -A
        git commit -m "Synchronizing completion at $TODAY"
        git push -u origin develop
     fi
}

add_yum_repo() {
cat > /etc/yum.repos.d/google-cloud-sdk.repo <<EOF
[google-cloud-sdk]
name=Google Cloud SDK
baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
       https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
}

add_apt_source(){
    export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
    echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
}

install_sdk() {
    local OS_VERSION=$(grep -Po '(?<=^ID=")\w+' /etc/os-release)
    local OS_VERSION=${OS_VERSION:-ubuntu}
    if [[ $OS_VERSION =~ "centos" ]];then
        if ! [ -f /etc/yum.repos.d/google-cloud-sdk.repo ];then
            add_yum_repo
            yum -y install google-cloud-sdk
        else
            echo "gcloud is installed"
        fi
    elif [[ $OS_VERSION =~ "ubuntu" ]];then
        if ! [ -f /etc/apt/sources.list.d/google-cloud-sdk.list ];then
            add_apt_source
            sudo apt-get -y update && sudo apt-get -y install google-cloud-sdk
        else
             echo "gcloud is installed"
        fi
    fi
}

auth_sdk(){
    local AUTH_COUNT=$(gcloud auth list --format="get(account)"|wc -l)
    [ "$AUTH_COUNT" -eq 0 ] && gcloud auth activate-service-account --key-file=$HOME/gcloud.config.json ||
        echo "gcloud service account is exsits"
}


#  GCR_IMAGE_NAME  tag  REPO_IMAGE_NAME
image_tag(){
    docker pull $1:$2
    docker tag $1:$2 $3:$2
    docker rmi $1:$2
}

img_clean(){
    local domain=$1 namespace=$2 image_name=$3
    local Prefix=$domain$interval$namespace$interval
    shift 3
    while read img tag null;do
        docker push $img:$tag;docker rmi $img:$tag;
        [ "$tag" != latest ] && echo $domain/$namespace/$image_name:$tag > $domain/$namespace/$image_name/$tag ||
            $@ $domain/$namespace/$image_name > $domain/$namespace/$image_name/$tag
        git_commit
    done < <(docker images --format {{.Repository}}' '{{.Tag}}' '{{.Size}} | awk -vcut=$MY_REPO/$Prefix '$0~cut{print $0 | "sort -hk3" }')
    git_commit
}

# google::name(){
#     gcloud container images list --repository=$@ --format="value(NAME)"
# }
# google::tag(){
#     gcloud container images list-tags $@  --format="get(TAGS)" --filter='tags:*' | sed 's#;#\n#g'
# }
# google::latest::digest(){
#     gcloud container images list-tags --format='get(DIGEST)' $@ --filter="tags=latest"
# }

google::name(){
    curl -XPOST -ks 'https://console.cloud.google.com/m/gcr/entities/list' \
           -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.7 Safari/537.36' \
           -H 'Content-Type: application/json;charset=UTF-8' \
           -H 'Accept: application/json, text/plain, */*' \
           --data-binary ['"'"${@#*/}"'"']   |
        awk -vio=$@ -F'"' '/"/{if(NR==3){if(!a[$4]++)print io"/"$4}else{if(!a[$2]++)print io"/"$2}}'
}
google::tag(){
    read null ns name< <(tr '/' ' '<<<$@)
    curl -ks -XGET https://gcr.io/v2/${ns}/${name}/tags/list | jq -r .tags[]
}
google::latest_digest(){
    read null ns name< <(tr '/' ' '<<<$@)
    curl -ks -XGET https://gcr.io/v2/${ns}/${name}/tags/list | jq -r '.manifest | with_entries(select(.value.tag[] == "latest"))|keys[]'
}

#quay::name(){
#    NS=${1#*/}
#    curl -sL 'https://quay.io/api/v1/repository?public=true&namespace='${NS} | jq -r '"quay.io/'${NS}'/"'" + .repositories[].name"
#}
#quay::tag(){
#    curl -sL "https://quay.io/api/v1/repository/${@#*/}?tag=info"  | jq -r .tags[].name
#}
#quay::latest_digest(){
# #    curl -sL "https://quay.io/api/v1/repository/prometheus/alertmanager/tag" | jq -r '.tags[]|select(.name == "latest" and (.|length) == 5 ).manifest_digest'
#   curl -sL "https://quay.io/api/v1/repository/${@#*/}?tag=info" | jq -r '.tags[]|select(.name == "latest" and (has("end_ts")|not) ).manifest_digest'
#}


image_pull(){
    REPOSITORY=$1
    echo 'Sync the '$REPOSITORY
    shift
    domain=${REPOSITORY%%/*}
    namespace=${REPOSITORY##*/}
    Prefix=$domain$interval$namespace$interval
    # REPOSITORY is the name of the dir,convert the '/' to '.',and cut the last '.'
    [ ! -d "$domain/$namespace" ] && mkdir -p $domain/$namespace
    while read SYNC_IMAGE_NAME;do
        image_name=${SYNC_IMAGE_NAME##*/}
        MY_REPO_IMAGE_NAME=${Prefix}${image_name}
        [ ! -d "$domain/$namespace/$image_name" ] && mkdir -p "$domain/$namespace/$image_name"
        [ -f "$domain/$namespace/$image_name"/latest ] && mv $domain/$namespace/$image_name/latest{,.old}
        while read tag;do
        #处理latest标签
            [[ "$tag" == latest && -f "$domain/$namespace/$image_name"/latest.old ]] && {
                $@::latest_digest $SYNC_IMAGE_NAME > $domain/$namespace/$image_name/latest
                diff $domain/$namespace/$image_name/latest{,.old} &>/dev/null &&
                    { rm -f $domain/$namespace/$image_name/latest.old;continue; } ||
                      rm $domain/$namespace/$image_name/latest{,.old}
            }
            [ -f "$domain/$namespace/$image_name/$tag" ] && { trvis_live;continue; }
            [[ $(df -h| awk  '$NF=="/"{print +$5}') -ge "$max_per" || -n $(sync_commit_check) ]] && { wait;img_clean $domain $namespace $image_name $@::latest_digest; }
            read -u5
            {
                [ -n "$tag" ] && image_tag $SYNC_IMAGE_NAME $tag $MY_REPO/$MY_REPO_IMAGE_NAME
                echo >&5
            }&
        done < <($@::tag $SYNC_IMAGE_NAME)
        wait
        img_clean $domain $namespace $image_name $@::latest_digest
    done < <($@::name $REPOSITORY)
}

sync_commit_check(){
    [[ $(( (`date +%s` - start_time)/60 )) -gt 40 || -n "$(docker images | awk '$NF~"GB"')" ]] &&
        echo ture || false
}

# img_name tag
hub_tag_exist(){
    curl -s https://hub.docker.com/v2/repositories/${MY_REPO}/$1/tags/$2/ | jq -r .name
}


trvis_live(){
    [ $(( (`date +%s` - live_start_time)/60 )) -ge 8 ] && { live_start_time=$(date +%s);echo 'for live in the travis!'; }
}

sync_domain_repo(){
    path=${1%/}
    local name tag
    while read name tag;do
        img_name=$( sed 's#/#'"$interval"'#g'<<<$name )
        trvis_live       
        read -u5
        {
            [ "$( hub_tag_exist $img_name $tag )" == null ] && rm -f $name/$tag
            echo >&5
        }&
    done < <( find $path/ -type f | sed 's#/# #3' )
    wait
    git_commit
}


main(){
    
    [ -z "$start_time" ] && start_time=$(date +%s)
    git_init
    # install_sdk
    # auth_sdk
    Multi_process_init $(( max_process * 4 ))
    live_start_time=$(date +%s)
    read sync_time < sync_check
    [ $(( (`date +%s` - sync_time)/3600 )) -ge 6 ] && {
        [ ! -f sync_list_ns ] && ls gcr.io > sync_list_ns
        allns=(`xargs -n1 < sync_list_ns`)

        for ns in ${allns[@]};do 
            [ ! -f sync_list_name ] && ls gcr.io/$ns > sync_list_name
            allname=(`xargs -n1 < sync_list_name`)
            for name in ${allname[@]};do
                sync_domain_repo gcr.io/$ns/$name
                perl  -i -lne 'print if $_ ne "'$name'"' sync_list_name
            done
            rm -f sync_list_name
            perl  -i -lne 'print if $_ ne "'$ns'"' sync_list_ns
        done
        rm -f sync_list_ns
        echo the sync has done! 
        date +%s > sync_check
        git_commit
    }
    exec 5>&-;exec 5<&-
    
    Multi_process_init $max_process

    GOOLE_NAMESPACE=(`xargs -n1 < $google_list`)
    for repo in ${GOOLE_NAMESPACE[@]};do
        image_pull gcr.io/$repo google
        sed -i '/'"$repo"'/d' $google_list;echo "$repo" >> $google_list
    done

    exec 5>&-;exec 5<&-
    
    COMMIT_FILES_COUNT=$(git status -s|wc -l)
    TODAY=$(date +%F)
    if [ $COMMIT_FILES_COUNT -ne 0 ];then
        git add -A
        git commit -m "Synchronizing completion at $TODAY"
        git push -u origin develop
    fi
}

main

          接着,在项目根目录建一个文件夹 repo,在文件夹内建一个文件,起名为 google,文件内容为镜像 NAMESPACE

          我这里只同步 google_containers 下的镜像,因此文件内容为

google_containers

           如果你还需要同步其他 NAMESPACE,每行写一个 NAMESPACE 即可

           然后就可以将该项目推送到 GitHub 了

# git add .
# git commit -m "init"
# git push origin master

2.5  启动自动构建

       使用 GitHub 账号登录 Travis CI

       

      选择上一步建立的 GitHub 项目,开启自动构建,同时点击 Settings

      

     设置定时构建

      

      选择 Current Tab 页,也可以手动触发构建过程

      

       免费的 Travis CI 最长构建时间为 50 分钟,这通常是同步不完的,没关系,反复执行构建过程就行了

       已经同步过的镜像不会重复同步

       

       去你的 DockerHub 账户下查看下,应该已经有镜像同步过来了

        

        因为 DockerHub 不允许名称中包含 " / " ,因此这里使用了 " . " 作为连接符

        使用的时候需要重命名下,例如:

# docker pull shidaqiu/gcr.io.google_containers.cloud-controller-manager:v1.11.1
# docker tag shidaqiu/gcr.io.google_containers.cloud-controller-manager:v1.11.1 gcr.io/google_containers/cloud-controller-manager:v1.11.1

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值