通过golang实现一个简单的镜像下载工具,用来解决中国区无法使用常用镜像的问题
总体步骤
- 启动一台海外区域的ec2实例
- 安装docker和awscli
- 配置凭证访问国内ecr仓库
- 编写web服务实现镜像转换和自动推送
以上步骤也可以采用ecs特权容器的方式托管,本文中使用ec2完成
配置环境
sudo yum install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo groupadd docker
sudo usermod -aG docker ec2-user
编写web服务
前端搜索
直接用chatgpt生成前端
<!DOCTYPE html>
<html>
<head>
<title>ToImage</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
margin: 0;
padding: 0;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
}
.title {
font-size: 48px;
font-weight: bold;
margin-bottom: 30px;
color: #4CAF50;
text-shadow: 2px 2px 5px #ccc;
}
.parent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 30px;
border-radius: 10px;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
background-color: #fff;
width: 80%;
max-width: 600px;
}
.search {
width: 100%;
padding: 10px;
font-size: 24px;
border: 2px solid #ccc;
border-radius: 10px;
outline: none;
margin-bottom: 20px;
}
.btn {
width: 100%;
padding: 10px;
font-size: 24px;
border: none;
border-radius: 10px;
background-color: #4CAF50;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
text-shadow: 2px 2px 5px #ccc;
}
.btn:hover {
background-color: #3e8e41;
}
@media screen and (max-width: 600px) {
.title {
font-size: 36px;
}
.search {
font-size: 18px;
}
.btn {
font-size: 18px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="title">ToImage</div>
<form class="parent" method="get" action="/upload"> <input type="text" class="search"
placeholder="Please Input..." name="iamgename" /> <input type="submit" class="btn" value="Search" />
</form>
</div>
</body>
</html>
前端输出
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Toimage</title>
<style>
/* 样式表 */
.copy-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 5px;
text-align: center;
text-decoration: none;
display: block; /* 修改按钮为块级元素 */
font-size: 16px;
font-weight: bold; /* 加粗字体 */
margin-top: 10px; /* 调整按钮与文字之间的距离 */
cursor: pointer;
}
.copy-btn:hover {
background-color: #3e8e41;
}
/* 居中显示 */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
/* 加粗字体 */
p {
font-weight: bold;
}
</style>
<script>
/* 复制文本函数 */
function copyText(text) {
var textarea = document.createElement('textarea');
textarea.textContent = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
</head>
<body>
<div class="container">
<p>{{.ImageName}}</p>
<button class="copy-btn" onclick="copyText('{{.ImageName}}')">复制</button> <!-- 将按钮放在段落下面 -->
<p>{{.Command}}</p>
<button class="copy-btn" onclick="copyText('{{.Command}}')">复制</button> <!-- 将按钮放在段落下面 -->
</div>
</body>
</html>
后端服务
- 使用docker的goclient实现pull,tag和push image
- 使用aws-go-sdk完成ecr认证
- 在环境中配置aksk(需要ecr相关权限),自动获取对应的账户id,默认为北京区仓库
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"io"
"os"
"strings"
"log"
"net/http"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
type Output struct {
ImageName string
Command string
}
func main() {
http.HandleFunc("/", index)
http.HandleFunc("/upload", myrepo)
log.Println("The server is listening on 0.0.0.0:5455")
http.ListenAndServe(":5455", nil)
}
// index
func index(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("/home/ec2-user/toimage/index.html")
t.Execute(w, "hi")
}
// toimage
func myrepo(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("/home/ec2-user/toimage/output.html")
log.Println("recieving request")
imagename := r.URL.Query().Get("iamgename")
if imagename == "" {
log.Println("Please input image name and tag")
return
}
// Get account id
sts_client := sts.New(session.New(), aws.NewConfig().WithRegion("cn-north-1"))
input := &sts.GetCallerIdentityInput{}
stsresult, err := sts_client.GetCallerIdentity(input)
if err != nil {
log.Println("Can not get account id")
// log.Println("Can not get account id")
return
}
accountid := *stsresult.Account
log.Println("Your destination account is", accountid)
// Initialize
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.WithVersion("1.41"))
if err != nil {
log.Println("Unable to create docker client")
return
}
// list images
// images, err := cli.ImageList(ctx, types.ImageListOptions{})
// if err != nil {
// log.Println("Unable to list images")
// return
// }
// for _, image := range images {
// if len(image.RepoTags) > 0 && imagename == image.RepoTags[0] {
// log.Printf("The image '%s' already exists on the host!", imagename)
// return
// }
// }
// pull image
pullReader, pull_err := cli.ImagePull(ctx, imagename, types.ImagePullOptions{})
if pull_err != nil {
log.Printf("Failed to pull image '%s': %s\n", imagename, err.Error())
return
}
defer pullReader.Close()
io.Copy(os.Stdout, pullReader)
log.Printf("Successcully pull image %s", imagename)
//tag image
parts := strings.Split(imagename, "/")
tempstr := parts[len(parts)-1]
split := strings.Split(tempstr, ":")
shortname := split[0]
tag := ""
if len(split) == 2 {
tag = split[1]
} else {
tag = "latest"
}
tagimage := accountid + ".dkr.ecr.cn-north-1.amazonaws.com.cn/" + shortname + ":" + tag
log.Println("The image name is", tagimage)
log.Println("The image name is", tagimage)
log.Println("login with")
command := fmt.Sprintf("aws ecr get-login-password --region cn-north-1 | docker login --username AWS --password-stdin %s.dkr.ecr.cn-north-1.amazonaws.com.cn", accountid)
log.Println(command)
output := Output{
ImageName: tagimage,
Command: command,
}
t.Execute(w, output)
cli.ImageTag(ctx, imagename, tagimage)
// Create repo
mySession := session.Must(session.NewSession())
ecr_cli := ecr.New(mySession, aws.NewConfig().WithRegion("cn-north-1"))
create_input := &ecr.CreateRepositoryInput{
RepositoryName: aws.String(shortname),
}
ecrresult, err := ecr_cli.CreateRepository(create_input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ecr.ErrCodeRepositoryAlreadyExistsException:
log.Println(ecr.ErrCodeRepositoryAlreadyExistsException, aerr.Error())
default:
log.Println(aerr.Error())
}
} else {
log.Println(err.Error())
}
} else {
log.Println("Success creating repo", ecrresult.Repository.RepositoryArn)
}
log.Println("The repo name is", accountid+".dkr.ecr.cn-north-1.amazonaws.com.cn/"+shortname)
// Push image
ecr_client := ecr.New(session.New(), aws.NewConfig().WithRegion("cn-north-1"))
token_input := &ecr.GetAuthorizationTokenInput{}
result, err := ecr_client.GetAuthorizationToken(token_input)
if err != nil {
fmt.Println(err.Error())
}
token := *result.AuthorizationData[0].AuthorizationToken
decodedToken, err := base64.StdEncoding.DecodeString(token)
if err != nil {
fmt.Println("Error decoding token:", err)
return
}
passwd := strings.Split(string(decodedToken), ":")[1]
authConfig := types.AuthConfig{
Username: "AWS",
Password: passwd,
}
encodedJSON, _ := json.Marshal(authConfig)
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
push_out, push_err := cli.ImagePush(context.TODO(), fmt.Sprintf("%s.dkr.ecr.cn-north-1.amazonaws.com.cn/%s:%s", accountid, shortname, tag), types.ImagePushOptions{RegistryAuth: authStr})
if push_err != nil {
log.Println("Unable to push image")
return
}
io.Copy(os.Stdout, push_out)
log.Println("Image pushed to ECR successfully")
// Remove image
_, removeerr := cli.ImageRemove(ctx, tagimage, types.ImageRemoveOptions{})
if removeerr != nil {
log.Println("Failed to remover image", err)
return
}
log.Println("Clear temp image successfully")
// return info
log.Println("The image is successful pushed")
}
创建守护配置
sudo vim /etc/systemd/system/toimage.service
[Unit]
Description=my toimage daemon service
[Service]
Type=simple
Restart=always
RestartSec=5s
User=root
ExecStart=/home/ec2-user/toimage/toimage
[Install]
WantedBy=multi-user.target
启动服务
sudo systemctl daemon-reload
systemctl start toimage
systemctl enable toimage
测试拉取镜像