Skip to content

Commit e2024fa

Browse files
authored
Merge pull request #60 from lovc21/Feat/Distroless_image_as_base_image
Feat: add initial Distroless implementation
2 parents b8b71d2 + 782b6b1 commit e2024fa

File tree

3 files changed

+242
-76
lines changed

3 files changed

+242
-76
lines changed

Dockerfile

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ WORKDIR /app/avalanchego/
1414

1515
RUN /app/avalanchego/scripts/build.sh
1616

17-
FROM ubuntu:24.10
17+
RUN mkdir -p /app/conf/coston /app/conf/C /app/logs /app/db
18+
19+
WORKDIR /entrypoint
20+
COPY entrypoint/main.go .
21+
RUN go build -ldflags="-s -w" -o /out/entrypoint main.go
22+
23+
FROM gcr.io/distroless/base AS final
1824

1925
WORKDIR /app
2026

@@ -36,13 +42,12 @@ ENV HTTP_HOST=0.0.0.0 \
3642
EXTRA_ARGUMENTS="" \
3743
BOOTSTRAP_BEACON_CONNECTION_TIMEOUT="1m"
3844

39-
RUN apt-get update -y && \
40-
apt-get install -y curl jq
41-
42-
RUN mkdir -p /app/conf/coston /app/conf/C /app/logs /app/db
45+
COPY --from=build /app/conf /app/conf
46+
COPY --from=build /app/logs /app/logs
47+
COPY --from=build /app/db /app/db
4348

4449
COPY --from=build /app/avalanchego/build /app/build
45-
COPY entrypoint.sh /app/entrypoint.sh
50+
COPY --from=build /out/entrypoint /app/entrypoint
4651

4752
EXPOSE ${STAKING_PORT}
4853
EXPOSE ${HTTP_PORT}
@@ -51,7 +56,4 @@ VOLUME [ "${DB_DIR}" ]
5156
VOLUME [ "${LOG_DIR}" ]
5257
VOLUME [ "${CHAIN_CONFIG_DIR}" ]
5358

54-
HEALTHCHECK CMD curl --fail http://localhost:${HTTP_PORT}/ext/health || exit 1
55-
56-
ENTRYPOINT [ "/usr/bin/bash" ]
57-
CMD [ "/app/entrypoint.sh" ]
59+
ENTRYPOINT [ "/app/entrypoint" ]

entrypoint.sh

Lines changed: 0 additions & 66 deletions
This file was deleted.

entrypoint/main.go

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"strings"
13+
"time"
14+
)
15+
16+
type rpcRequest struct {
17+
JSONRPC string `json:"jsonrpc"`
18+
ID int `json:"id"`
19+
Method string `json:"method"`
20+
}
21+
type rpcResponse struct {
22+
Result json.RawMessage `json:"result"`
23+
}
24+
25+
func fetchPublicIP() (string, error) {
26+
resp, err := http.Get("https://flare.network/cdn-cgi/trace")
27+
if err != nil {
28+
return "", err
29+
}
30+
defer resp.Body.Close()
31+
32+
body, err := io.ReadAll(resp.Body)
33+
if err != nil {
34+
return "", err
35+
}
36+
37+
for _, line := range strings.Split(string(body), "\n") {
38+
if strings.HasPrefix(line, "ip=") {
39+
return strings.TrimPrefix(line, "ip="), nil
40+
}
41+
}
42+
43+
return "", fmt.Errorf("no ip= line in trace")
44+
}
45+
46+
func parseIPResult(raw json.RawMessage) (string, error) {
47+
var obj struct {
48+
IP json.RawMessage `json:"ip"`
49+
}
50+
if err := json.Unmarshal(raw, &obj); err == nil {
51+
var single string
52+
if err := json.Unmarshal(obj.IP, &single); err == nil {
53+
return single, nil
54+
}
55+
var arr []string
56+
if err := json.Unmarshal(obj.IP, &arr); err == nil {
57+
return strings.Join(arr, ","), nil
58+
}
59+
return "", fmt.Errorf("unexpected ip field format: %s", obj.IP)
60+
}
61+
var single string
62+
if err := json.Unmarshal(raw, &single); err == nil {
63+
return single, nil
64+
}
65+
var arr []string
66+
if err := json.Unmarshal(raw, &arr); err == nil {
67+
return strings.Join(arr, ","), nil
68+
}
69+
return "", fmt.Errorf("unexpected result format: %s", string(raw))
70+
}
71+
72+
func parseNodeIDResult(raw json.RawMessage) (string, error) {
73+
var obj struct {
74+
NodeID string `json:"nodeID"`
75+
}
76+
if err := json.Unmarshal(raw, &obj); err == nil {
77+
return obj.NodeID, nil
78+
}
79+
var s string
80+
if err := json.Unmarshal(raw, &s); err == nil {
81+
return s, nil
82+
}
83+
return "", fmt.Errorf("unexpected nodeID format: %s", string(raw))
84+
}
85+
86+
func rpcCall(client *http.Client, url, method string) (json.RawMessage, error) {
87+
body, _ := json.Marshal(rpcRequest{"2.0", 1, method})
88+
resp, err := client.Post(url, "application/json", bytes.NewReader(body))
89+
if err != nil {
90+
return nil, err
91+
}
92+
defer resp.Body.Close()
93+
var wrap rpcResponse
94+
if err := json.NewDecoder(resp.Body).Decode(&wrap); err != nil {
95+
return nil, err
96+
}
97+
return wrap.Result, nil
98+
}
99+
100+
func main() {
101+
if os.Getenv("AUTOCONFIGURE_PUBLIC_IP") == "1" {
102+
if os.Getenv("PUBLIC_IP") == "" {
103+
fmt.Fprintln(os.Stderr, "Autoconfiguring public IP")
104+
ip, err := fetchPublicIP()
105+
if err != nil {
106+
fmt.Fprintln(os.Stderr, "failed to get ip")
107+
os.Exit(1)
108+
}
109+
fmt.Fprintf(os.Stderr, "Got public address %s \n", ip)
110+
os.Setenv("PUBLIC_IP", ip)
111+
} else {
112+
msg := fmt.Sprintf(
113+
`/!\ AUTOCONFIGURE_PUBLIC_IP is enabled, but PUBLIC_IP is already `+
114+
`set to '%s'! Skipping autoconfigure and using current PUBLIC_IP value!`+"\n",
115+
os.Getenv("PUBLIC_IP"),
116+
)
117+
fmt.Fprint(os.Stderr, msg)
118+
}
119+
}
120+
121+
if os.Getenv("AUTOCONFIGURE_BOOTSTRAP") == "1" {
122+
endpoints := []string{os.Getenv("AUTOCONFIGURE_BOOTSTRAP_ENDPOINT")}
123+
if fb := os.Getenv("AUTOCONFIGURE_FALLBACK_ENDPOINTS"); fb != "" {
124+
for _, e := range strings.Split(fb, ",") {
125+
e = strings.TrimSpace(e)
126+
if e != "" {
127+
endpoints = append(endpoints, e)
128+
}
129+
}
130+
}
131+
132+
var endpoint string
133+
fmt.Fprintln(os.Stderr, "Trying provided bootstrap endpoints")
134+
client := http.Client{Timeout: 5 * time.Second}
135+
probe := []byte(`{"jsonrpc":"2.0","id":1,"method":"info.getNodeIP"}`)
136+
137+
for _, ep := range endpoints {
138+
fmt.Fprintf(os.Stderr, " Trying endpoint %s\n", ep)
139+
140+
resp, err := client.Post(ep, "application/json", bytes.NewReader(probe))
141+
if err != nil {
142+
fmt.Fprintf(os.Stderr, " error: %v\n", err)
143+
continue
144+
}
145+
defer resp.Body.Close()
146+
147+
if resp.StatusCode == http.StatusOK {
148+
endpoint = ep
149+
break
150+
}
151+
fmt.Fprintln(os.Stderr, " Failed! The endpoint is unreachable.")
152+
}
153+
154+
if endpoint == "" {
155+
fmt.Fprintln(os.Stderr, " None of provided bootstrap endpoints worked!")
156+
os.Exit(1)
157+
}
158+
fmt.Fprintln(os.Stderr, "found endpoint : ", endpoint)
159+
160+
fmt.Fprintln(os.Stderr, "Autoconfiguring bootstrap IPs and IDs")
161+
162+
rawIPs, err := rpcCall(&client, endpoint, "info.getNodeIP")
163+
if err != nil {
164+
fmt.Fprintln(os.Stderr, " getNodeIP RPC failed:", err)
165+
os.Exit(1)
166+
}
167+
bootstrap_IPs, err := parseIPResult(rawIPs)
168+
if err != nil {
169+
fmt.Fprintln(os.Stderr, " parsing IPs failed:", err)
170+
os.Exit(1)
171+
}
172+
173+
rawIDs, err := rpcCall(&client, endpoint, "info.getNodeID")
174+
if err != nil {
175+
fmt.Fprintln(os.Stderr, " getNodeID RPC failed:", err)
176+
os.Exit(1)
177+
}
178+
179+
bootstrap_IDs, err := parseNodeIDResult(rawIDs)
180+
if err != nil {
181+
fmt.Fprintln(os.Stderr, " parsing IDs failed:", err)
182+
os.Exit(1)
183+
}
184+
185+
fmt.Fprintf(os.Stderr, " Got bootstrap ips: '%s'\n", bootstrap_IPs)
186+
fmt.Fprintf(os.Stderr, " Got bootstrap ids: '%s'\n", bootstrap_IDs)
187+
188+
os.Setenv("BOOTSTRAP_IPS", bootstrap_IPs)
189+
os.Setenv("BOOTSTRAP_IDS", bootstrap_IDs)
190+
}
191+
192+
args := []string{
193+
"--http-host", os.Getenv("HTTP_HOST"),
194+
"--http-port", os.Getenv("HTTP_PORT"),
195+
"--staking-port", os.Getenv("STAKING_PORT"),
196+
"--public-ip", os.Getenv("PUBLIC_IP"),
197+
"--db-dir", os.Getenv("DB_DIR"),
198+
"--db-type", os.Getenv("DB_TYPE"),
199+
"--bootstrap-ips", os.Getenv("BOOTSTRAP_IPS"),
200+
"--bootstrap-ids", os.Getenv("BOOTSTRAP_IDS"),
201+
"--bootstrap-beacon-connection-timeout", os.Getenv("BOOTSTRAP_BEACON_CONNECTION_TIMEOUT"),
202+
"--chain-config-dir", os.Getenv("CHAIN_CONFIG_DIR"),
203+
"--log-dir", os.Getenv("LOG_DIR"),
204+
"--log-level", os.Getenv("LOG_LEVEL"),
205+
"--network-id", os.Getenv("NETWORK_ID"),
206+
}
207+
if extra := os.Getenv("EXTRA_ARGUMENTS"); extra != "" {
208+
args = append(args, strings.Fields(extra)...)
209+
}
210+
fmt.Fprintln(os.Stderr, args)
211+
path := "/app/build/avalanchego"
212+
213+
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
214+
fmt.Fprintln(os.Stderr, "file does not exist")
215+
os.Exit(1)
216+
} else {
217+
cmd := exec.Command(path, args...)
218+
cmd.Stdout = os.Stdout
219+
cmd.Stderr = os.Stderr
220+
cmd.Env = os.Environ()
221+
if err := cmd.Run(); err != nil {
222+
if exitErr, ok := err.(*exec.ExitError); ok {
223+
os.Exit(exitErr.ExitCode())
224+
}
225+
fmt.Fprintln(os.Stderr, "failed to start avalanchego:", err)
226+
os.Exit(1)
227+
}
228+
os.Exit(0)
229+
}
230+
}

0 commit comments

Comments
 (0)