Skip to content

Commit

Permalink
fix: prevent growing TCP connections by using a shared HTTP transport
Browse files Browse the repository at this point in the history
Previously, a new HTTP transport was created for each request to container registry, leading to an excessive number of open TCP connections that were not properly reused. This
commit addresses the issue by introducing a shared HTTP transport that is reused for all requests, thus improving resource management and connection reuse.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
  • Loading branch information
alexey-igrychev committed May 27, 2024
1 parent eec3002 commit 8fed3e7
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 33 deletions.
10 changes: 7 additions & 3 deletions pkg/docker_registry/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"math/rand"
"net/http"
"regexp"
"strings"
"time"
Expand All @@ -30,6 +31,8 @@ import (
type api struct {
InsecureRegistry bool
SkipTlsVerifyRegistry bool

httpTransport http.RoundTripper
}

type apiOptions struct {
Expand All @@ -41,6 +44,7 @@ func newAPI(options apiOptions) *api {
return &api{
InsecureRegistry: options.InsecureRegistry,
SkipTlsVerifyRegistry: options.SkipTlsVerifyRegistry,
httpTransport: newHttpTransport(options.SkipTlsVerifyRegistry),
}
}

Expand Down Expand Up @@ -501,7 +505,7 @@ func (api *api) defaultRemoteOptions(ctx context.Context) []remote.Option {
return []remote.Option{
remote.WithContext(ctx),
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithTransport(getHttpTransport(api.SkipTlsVerifyRegistry)),
remote.WithTransport(api.httpTransport),
}
}

Expand Down Expand Up @@ -600,15 +604,15 @@ func (api *api) writeToRemote(ctx context.Context, ref name.Reference, imageOrIn
ref, i,
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithProgress(c),
remote.WithTransport(getHttpTransport(api.SkipTlsVerifyRegistry)),
remote.WithTransport(api.httpTransport),
remote.WithContext(ctx),
)
case v1.ImageIndex:
go remote.WriteIndex(
ref, i,
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithProgress(c),
remote.WithTransport(getHttpTransport(api.SkipTlsVerifyRegistry)),
remote.WithTransport(api.httpTransport),
remote.WithContext(ctx),
)
default:
Expand Down
12 changes: 3 additions & 9 deletions pkg/docker_registry/common_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type doRequestBasicAuth struct {
password string
}

func doRequest(ctx context.Context, method, url string, body io.Reader, options doRequestOptions) (*http.Response, []byte, error) {
func doRequest(ctx context.Context, client *http.Client, method, url string, body io.Reader, options doRequestOptions) (*http.Response, []byte, error) {
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, nil, err
Expand All @@ -48,7 +48,7 @@ func doRequest(ctx context.Context, method, url string, body io.Reader, options
}

logboek.Context(ctx).Debug().LogF("--> %s %s\n", method, url)
resp, err := getHTTPClient(options.SkipTlsVerify).Do(req)
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
Expand All @@ -72,13 +72,7 @@ func doRequest(ctx context.Context, method, url string, body io.Reader, options
return resp, respBody, nil
}

func getHTTPClient(skipTlsVerify bool) *http.Client {
return &http.Client{
Transport: getHttpTransport(skipTlsVerify),
}
}

func getHttpTransport(skipTlsVerify bool) http.RoundTripper {
func newHttpTransport(skipTlsVerify bool) http.RoundTripper {
t := remote.DefaultTransport.(*http.Transport).Clone()

if skipTlsVerify {
Expand Down
16 changes: 11 additions & 5 deletions pkg/docker_registry/docker_hub_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import (
"net/http"
)

type dockerHubApi struct{}
type dockerHubApi struct {
httpClient *http.Client
}

func newDockerHubApi() dockerHubApi {
return dockerHubApi{}
return dockerHubApi{
httpClient: &http.Client{
Transport: newHttpTransport(false),
},
}
}

func (api *dockerHubApi) deleteRepository(ctx context.Context, account, project, token string) (*http.Response, error) {
Expand All @@ -21,7 +27,7 @@ func (api *dockerHubApi) deleteRepository(ctx context.Context, account, project,
project,
)

resp, _, err := doRequest(ctx, http.MethodDelete, url, nil, doRequestOptions{
resp, _, err := doRequest(ctx, api.httpClient, http.MethodDelete, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/json",
"Authorization": fmt.Sprintf("JWT %s", token),
Expand All @@ -40,7 +46,7 @@ func (api *dockerHubApi) deleteTag(ctx context.Context, account, project, tag, t
tag,
)

resp, _, err := doRequest(ctx, http.MethodDelete, url, nil, doRequestOptions{
resp, _, err := doRequest(ctx, api.httpClient, http.MethodDelete, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/json",
"Authorization": fmt.Sprintf("JWT %s", token),
Expand All @@ -61,7 +67,7 @@ func (api *dockerHubApi) getToken(ctx context.Context, username, password string
return "", nil, err
}

resp, respBody, err := doRequest(ctx, http.MethodPost, url, bytes.NewBuffer(body), doRequestOptions{
resp, respBody, err := doRequest(ctx, api.httpClient, http.MethodPost, url, bytes.NewBuffer(body), doRequestOptions{
Headers: map[string]string{
"Content-Type": "application/json",
},
Expand Down
16 changes: 11 additions & 5 deletions pkg/docker_registry/github_packages_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import (
"time"
)

type gitHubApi struct{}
type gitHubApi struct {
httpClient *http.Client
}

func newGitHubApi() gitHubApi {
return gitHubApi{}
return gitHubApi{
httpClient: &http.Client{
Transport: newHttpTransport(false),
},
}
}

type githubApiUser struct {
Expand Down Expand Up @@ -57,7 +63,7 @@ type githubApiUser struct {

func (api *gitHubApi) getUser(ctx context.Context, username, token string) (githubApiUser, *http.Response, error) {
url := fmt.Sprintf("https://api.github.com/users/%s", username)
resp, respBody, err := doRequest(ctx, http.MethodGet, url, nil, doRequestOptions{
resp, respBody, err := doRequest(ctx, api.httpClient, http.MethodGet, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/vnd.github.v3+json",
"Authorization": fmt.Sprintf("Bearer %s", token),
Expand Down Expand Up @@ -135,7 +141,7 @@ type githubApiVersion struct {
func (api *gitHubApi) getContainerPackageVersionListInBatches(ctx context.Context, url, token string, f func([]githubApiVersion) error) (*http.Response, error) {
for page := 1; true; page++ {
pageUrl := url + fmt.Sprintf("?page=%d&per_page=100", page)
resp, respBody, err := doRequest(ctx, http.MethodGet, pageUrl, nil, doRequestOptions{
resp, respBody, err := doRequest(ctx, api.httpClient, http.MethodGet, pageUrl, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/vnd.github.v3+json",
"Authorization": fmt.Sprintf("Bearer %s", token),
Expand Down Expand Up @@ -164,7 +170,7 @@ func (api *gitHubApi) getContainerPackageVersionListInBatches(ctx context.Contex
}

func (api *gitHubApi) deleteContainerPackage(ctx context.Context, url, token string) (*http.Response, error) {
resp, _, err := doRequest(ctx, http.MethodDelete, url, nil, doRequestOptions{
resp, _, err := doRequest(ctx, api.httpClient, http.MethodDelete, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/vnd.github.v3+json",
"Authorization": fmt.Sprintf("Bearer %s", token),
Expand Down
2 changes: 1 addition & 1 deletion pkg/docker_registry/gitlab_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (r *gitLabRegistry) customDeleteRepoImage(endpointFormat, reference string,
}

scope := scopeFunc(ref)
tr, err := transport.New(ref.Context().Registry, auth, getHttpTransport(false), scope)
tr, err := transport.New(ref.Context().Registry, auth, r.api.httpTransport, scope)
if err != nil {
return err
}
Expand Down
12 changes: 9 additions & 3 deletions pkg/docker_registry/harbor_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import (
"path"
)

type harborApi struct{}
type harborApi struct {
httpClient *http.Client
}

func newHarborApi() harborApi {
return harborApi{}
return harborApi{
httpClient: &http.Client{
Transport: newHttpTransport(false),
},
}
}

func (api *harborApi) DeleteRepository(ctx context.Context, hostname, repository, username, password string) (*http.Response, error) {
Expand All @@ -22,7 +28,7 @@ func (api *harborApi) DeleteRepository(ctx context.Context, hostname, repository
u.Path = path.Join(u.Path, "repositories", repository)
url := u.String()

resp, _, err := doRequest(ctx, http.MethodDelete, url, nil, doRequestOptions{
resp, _, err := doRequest(ctx, api.httpClient, http.MethodDelete, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/json",
},
Expand Down
12 changes: 9 additions & 3 deletions pkg/docker_registry/quay_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import (
"path"
)

type quayApi struct{}
type quayApi struct {
httpClient *http.Client
}

func newQuayApi() quayApi {
return quayApi{}
return quayApi{
httpClient: &http.Client{
Transport: newHttpTransport(false),
},
}
}

func (api *quayApi) DeleteRepository(ctx context.Context, hostname, namespace, repository, token string) (*http.Response, error) {
Expand All @@ -26,7 +32,7 @@ func (api *quayApi) DeleteRepository(ctx context.Context, hostname, namespace, r
reqAccept := "application/json"
reqAuthorization := fmt.Sprintf("Bearer %s", token)

resp, _, err := doRequest(ctx, http.MethodDelete, reqUrl, nil, doRequestOptions{
resp, _, err := doRequest(ctx, api.httpClient, http.MethodDelete, reqUrl, nil, doRequestOptions{
Headers: map[string]string{
"Accept": reqAccept,
"Authorization": reqAuthorization,
Expand Down
14 changes: 10 additions & 4 deletions pkg/docker_registry/selectel_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ import (
parallelConstant "github.com/werf/werf/pkg/util/parallel/constant"
)

type selectelApi struct{}
type selectelApi struct {
httpClient *http.Client
}

func newSelectelApi() selectelApi {
return selectelApi{}
return selectelApi{
httpClient: &http.Client{
Transport: newHttpTransport(false),
},
}
}

func (api *selectelApi) makeApiUrl(hostname, registryId string, pathParts ...string) (string, error) {
Expand Down Expand Up @@ -164,7 +170,7 @@ func (api *selectelApi) getTags(ctx context.Context, hostname, registryId, repos
return nil, nil, err
}

resp, respBody, err := doRequest(ctx, http.MethodGet, url, nil, doRequestOptions{
resp, respBody, err := doRequest(ctx, api.httpClient, http.MethodGet, url, nil, doRequestOptions{
Headers: map[string]string{
"Accept": "application/json",
"X-Auth-Token": token,
Expand All @@ -189,7 +195,7 @@ func (api *selectelApi) getTags(ctx context.Context, hostname, registryId, repos

func (api *selectelApi) doRequest(ctx context.Context, method, url string, body io.Reader, options doRequestOptions) (*http.Response, []byte, error) {
var seconds int
resp, respBody, err := doRequest(ctx, method, url, body, options)
resp, respBody, err := doRequest(ctx, api.httpClient, method, url, body, options)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusTooManyRequests {
if resp.Header.Get("Retry-After") != "" {
Expand Down

0 comments on commit 8fed3e7

Please sign in to comment.