Skip to content

Commit

Permalink
feat: add basic dns client to kapinger (#859)
Browse files Browse the repository at this point in the history
# Description

Simple nslookup on burst

## Related Issue



## Checklist

- [ ] I have read the [contributing
documentation](https://retina.sh/docs/contributing).
- [ ] I signed and signed-off the commits (`git commit -S -s ...`). See
[this
documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
on signing commits.
- [ ] I have correctly attributed the author(s) of the code.
- [ ] I have tested the changes locally.
- [ ] I have followed the project's style guidelines.
- [ ] I have updated the documentation, if necessary.
- [ ] I have added tests, if applicable.

## Screenshots (if applicable) or Testing Completed

Please add any relevant screenshots or GIFs to showcase the changes
made.

## Additional Notes

Add any additional notes or context about the pull request here.

---

Please refer to the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more
information on how to contribute to this project.
  • Loading branch information
matmerr authored Oct 29, 2024
1 parent 07f4463 commit 32674dd
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 97 deletions.
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ RETINA_OPERATOR_IMAGE = $(IMAGE_NAMESPACE)/retina-operator
RETINA_INTEGRATION_TEST_IMAGE = $(IMAGE_NAMESPACE)/retina-integration-test
RETINA_PROTO_IMAGE = $(IMAGE_NAMESPACE)/retina-proto-gen
RETINA_GO_GEN_IMAGE = $(IMAGE_NAMESPACE)/retina-go-gen
KAPINGER_IMAGE = $(IMAGE_NAMESPACE)/kapinger
KAPINGER_IMAGE = kapinger

skopeo-export: # util target to copy a container from containers-storage to the docker daemon.
skopeo copy \
Expand Down Expand Up @@ -307,6 +307,15 @@ retina-operator-image: ## build the retina linux operator image.
APP_INSIGHTS_ID=$(APP_INSIGHTS_ID) \
CONTEXT_DIR=$(REPO_ROOT)

kapinger-image:
docker buildx build --builder retina --platform windows/amd64 --target windows-amd64 -t $(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-windows-amd64 ./hack/tools/kapinger/ --push
docker buildx build --builder retina --platform linux/amd64 --target linux-amd64 -t $(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-linux-amd64 ./hack/tools/kapinger/ --push
docker buildx build --builder retina --platform linux/arm64 --target linux-arm64 -t $(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-linux-arm64 ./hack/tools/kapinger/ --push
docker buildx imagetools create -t $(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG) \
$(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-windows-amd64 \
$(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-linux-amd64 \
$(IMAGE_REGISTRY)/$(KAPINGER_IMAGE):$(TAG)-linux-arm64

proto-gen: ## generate protobuf code
docker build --platform=linux/amd64 \
-t $(IMAGE_REGISTRY)/$(RETINA_PROTO_IMAGE):$(RETINA_PLATFORM_TAG) \
Expand Down
1 change: 1 addition & 0 deletions hack/tools/kapinger/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dockerfile
24 changes: 21 additions & 3 deletions hack/tools/kapinger/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
FROM mcr.microsoft.com/oss/go/microsoft/golang:1.22 AS builder
FROM --platform=linux/amd64 mcr.microsoft.com/oss/go/microsoft/golang:1.22 AS builder

WORKDIR /build
ADD . .
RUN go mod download

# Build for Linux
RUN CGO_ENABLED=0 GOOS=linux go build -o kapinger .

FROM scratch
WORKDIR /build
# Build for Windows
RUN CGO_ENABLED=0 GOOS=windows go build -o kapinger.exe .

# Build for ARM64 Linux
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o kapinger-arm64 .

FROM --platform=linux/amd64 scratch AS linux-amd64
WORKDIR /app
COPY --from=builder /build/kapinger .
CMD ["./kapinger"]

FROM --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 AS windows-amd64
WORKDIR /app
COPY --from=builder /build/kapinger.exe .
ENTRYPOINT [ "cmd.exe" ]
CMD [ "/c", "kapinger.exe" ]

FROM --platform=linux/arm64 scratch AS linux-arm64
WORKDIR /app
COPY --from=builder /build/kapinger-arm64 .
CMD ["./kapinger-arm64"]
16 changes: 0 additions & 16 deletions hack/tools/kapinger/Dockerfile.windows

This file was deleted.

9 changes: 9 additions & 0 deletions hack/tools/kapinger/clients/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package clients

import (
"context"
)

type Client interface {
MakeRequests(ctx context.Context) error
}
45 changes: 45 additions & 0 deletions hack/tools/kapinger/clients/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package clients

import (
"context"
"fmt"
"log"
"net"
"time"
)

type KapingerDNSClient struct {
volume int
interval time.Duration
}

func NewKapingerDNSClient(volume int, interval time.Duration) *KapingerDNSClient {
return &KapingerDNSClient{
interval: time.Duration(interval),
volume: volume,
}
}

func (k *KapingerDNSClient) MakeRequests(ctx context.Context) error {
ticker := time.NewTicker(k.interval)
for {
select {
case <-ctx.Done():
log.Printf("DNS client context done")
return nil
case <-ticker.C:
go func() {
for i := 0; i < k.volume; i++ {
domain := "retina.sh"

ips, err := net.LookupIP(domain)
if err != nil {
fmt.Printf("dns client: could not get IPs: %v\n", err)
return
}
log.Printf("dns client: resolved %s to %s\n", domain, ips)
}
}()
}
}
}
151 changes: 82 additions & 69 deletions hack/tools/kapinger/clients/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ type KapingerHTTPClient struct {
client http.Client
clientset *kubernetes.Clientset
labelselector string
ips []string
urls []string
port int
targettype TargetType
volume int
interval time.Duration
}

func NewKapingerHTTPClient(clientset *kubernetes.Clientset, labelselector string, httpPort int) (*KapingerHTTPClient, error) {
func NewKapingerHTTPClient(clientset *kubernetes.Clientset, labelselector string, volume int, interval time.Duration, httpPort int) (*KapingerHTTPClient, error) {
k := KapingerHTTPClient{
client: http.Client{
Transport: &http.Transport{
Expand All @@ -44,6 +46,8 @@ func NewKapingerHTTPClient(clientset *kubernetes.Clientset, labelselector string
labelselector: labelselector,
clientset: clientset,
port: httpPort,
volume: volume,
interval: interval,
}

targettype := os.Getenv(envTargetType)
Expand All @@ -53,105 +57,114 @@ func NewKapingerHTTPClient(clientset *kubernetes.Clientset, labelselector string
k.targettype = Service
}

err := k.getIPS()
var err error
switch k.targettype {
case Service:
k.urls, err = k.getServiceURLs()
if err != nil {
return nil, fmt.Errorf("error getting service URLs: %w", err)
}

case Pod:
k.urls, err = k.getPodURLs()

default:
return nil, fmt.Errorf("env TARGET_TYPE must be \"service\" or \"pod\"")
}
if err != nil {
return nil, fmt.Errorf("error getting IPs: %w", err)
}

return &k, nil
}
func (k *KapingerHTTPClient) MakeRequests(ctx context.Context, volume int, interval time.Duration) error {
ticker := time.NewTicker(interval)

func (k *KapingerHTTPClient) MakeRequests(ctx context.Context) error {
ticker := time.NewTicker(k.interval)
for {
select {
case <-ctx.Done():
log.Printf("HTTP client context done")
return nil
case <-ticker.C:
go func() {
for i := 0; i < volume; i++ {
err := k.makeRequest()
if err != nil {
log.Printf("error making request: %v", err)
for i := 0; i < k.volume; i++ {
for _, url := range k.urls {
body, err := k.makeRequest(ctx, url)
if err != nil {
log.Printf("error making request: %v", err)
} else {
log.Printf("response from %s: %s\n", url, string(body))
}
}
}
}()
}
}
}

func (k *KapingerHTTPClient) makeRequest() error {
for _, ip := range k.ips {
url := fmt.Sprintf("http://%s:%d", ip, k.port)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}

// Set the "Connection" header to "close"
req.Header.Set("Connection", "close")
func (k *KapingerHTTPClient) makeRequest(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, http.NoBody)
if err != nil {
return nil, err
}

// Send the request
resp, err := k.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Set the "Connection" header to "close"
req.Header.Set("Connection", "close")

body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body from %s: %v", url, err)
return err
}
log.Printf("Response from %s: %s\n", url, string(body))
// Send the request
resp, err := k.client.Do(req)
if err != nil {
return nil, err
}
return nil
}

func (k *KapingerHTTPClient) getIPS() error {
ips := []string{}
defer resp.Body.Close()

switch k.targettype {
case Service:
services, err := k.clientset.CoreV1().Services(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{
LabelSelector: k.labelselector,
})
if err != nil {
return fmt.Errorf("error getting services: %w", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body from %s: %v", url, err)
return nil, err
}

// Extract the Service cluster IP addresses
return body, nil
}

for _, svc := range services.Items {
ips = append(ips, svc.Spec.ClusterIP)
}
log.Println("using service IPs:", ips)
func (k *KapingerHTTPClient) getServiceURLs() ([]string, error) {
urls := []string{}
services, err := k.clientset.CoreV1().Services(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{
LabelSelector: k.labelselector,
})
if err != nil {
return urls, fmt.Errorf("error getting services: %w", err)
}

case Pod:
err := waitForPodsRunning(k.clientset, k.labelselector)
if err != nil {
return fmt.Errorf("error waiting for pods to be in Running state: %w", err)
}
// Extract the Service cluster IP addresses

// Get all pods in the cluster with label app=agnhost
pods, err := k.clientset.CoreV1().Pods(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{
LabelSelector: k.labelselector,
})
if err != nil {
return fmt.Errorf("error getting pods: %w", err)
}
for svc := range services.Items {
urls = append(urls, fmt.Sprintf("http://%s.%s.svc.cluster.local:%d/", services.Items[svc].Name, services.Items[svc].Namespace, k.port))
}
log.Printf("using service URLs: %+v", urls)
return urls, nil
}

for _, pod := range pods.Items {
ips = append(ips, pod.Status.PodIP)
}
func (k *KapingerHTTPClient) getPodURLs() ([]string, error) {
urls := []string{}
err := waitForPodsRunning(k.clientset, k.labelselector)
if err != nil {
return nil, fmt.Errorf("error waiting for pods to be in Running state: %w", err)
}

log.Printf("using pod IPs: %v", ips)
default:
return fmt.Errorf("env TARGET_TYPE must be \"service\" or \"pod\"")
// Get all pods in the cluster with label app=agnhost
pods, err := k.clientset.CoreV1().Pods(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{
LabelSelector: k.labelselector,
})
if err != nil {
return nil, fmt.Errorf("error getting pods: %w", err)
}

k.ips = ips
return nil
for _, pod := range pods.Items {
urls = append(urls, fmt.Sprintf("http://%s:%d", pod.Status.PodIP, k.port))
}
log.Printf("using pod URL's: %+v", urls)
return urls, nil
}

// waitForPodsRunning waits for all pods with the specified label to be in the Running phase
Expand Down
1 change: 1 addition & 0 deletions hack/tools/kapinger/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions hack/tools/kapinger/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
Loading

0 comments on commit 32674dd

Please sign in to comment.