Mizu install mode (#566)

This commit is contained in:
RoyUP9 2021-12-28 15:44:37 +02:00 committed by GitHub
parent b039c2abad
commit de046c65b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 207 additions and 449 deletions

View File

@ -179,9 +179,9 @@ tap
proxy-host: 0.0.0.0 proxy-host: 0.0.0.0
and when changed it will support accessing by IP and when changed it will support accessing by IP
### Run in daemon mode ### Install Mizu standalone
Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run
indefinitely in the cluster. indefinitely in the cluster.
For more information please refer to [DAEMON MODE](docs/DAEMON_MODE.md) For more information please refer to [INSTALL STANDALONE](docs/INSTALL_STANDALONE.md)

View File

@ -880,251 +880,3 @@ func TestTapDumpLogs(t *testing.T) {
return return
} }
} }
func TestDaemonSeeTraffic(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
tests := []int{50}
for _, entriesCount := range tests {
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapDaemonCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
tapNamespace := getDefaultTapNamespace()
tapDaemonCmdArgs = append(tapDaemonCmdArgs, tapNamespace...)
tapCmd := exec.Command(cliPath, tapDaemonCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("error occured while running the tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view command, err: %v", err)
return
}
apiServerUrl := getApiServerUrl(defaultApiServerPort)
if err := waitTapPodsReady(apiServerUrl); err != nil {
t.Errorf("failed to start tap pods on time, err: %v", err)
return
}
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
for i := 0; i < entriesCount; i++ {
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
t.Errorf("failed to send proxy request, err: %v", requestErr)
return
}
}
entriesCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
entries, err := getDBEntries(timestamp, entriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
entry := entries[0]
entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, entry["id"])
requestResult, requestErr := executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
if requestResult == nil {
return fmt.Errorf("unexpected nil entry result")
}
return nil
}
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
})
}
}
func TestDaemonMultipleNamespacesSeePods(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
expectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests"},
{Name: "httpbin2", Namespace: "mizu-tests"},
{Name: "httpbin", Namespace: "mizu-tests2"},
}
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view command, err: %v", err)
return
}
apiServerUrl := getApiServerUrl(defaultApiServerPort)
if err := waitTapPodsReady(apiServerUrl); err != nil {
t.Errorf("failed to start tap pods on time, err: %v", err)
return
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}
func TestDaemonSingleNamespaceSeePods(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
expectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests"},
{Name: "httpbin2", Namespace: "mizu-tests"},
}
unexpectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests2"},
}
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view command, err: %v", err)
return
}
apiServerUrl := getApiServerUrl(defaultApiServerPort)
if err := waitTapPodsReady(apiServerUrl); err != nil {
t.Errorf("failed to start tap pods on time, err: %v", err)
return
}
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
for _, unexpectedPod := range unexpectedPods {
if isPodDescriptorInPodArray(pods, unexpectedPod) {
t.Errorf("unexpected result - unexpected pod found, pod namespace: %v, pod name: %v", unexpectedPod.Namespace, unexpectedPod.Name)
return
}
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}

View File

@ -13,7 +13,6 @@ import (
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"testing"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -105,10 +104,6 @@ func getDefaultTapCommandArgs() []string {
return append([]string{tapCommand}, defaultCmdArgs...) return append([]string{tapCommand}, defaultCmdArgs...)
} }
func getDefaultTapCommandArgsWithDaemonMode() []string {
return append(getDefaultTapCommandArgs(), "--daemon")
}
func getDefaultTapCommandArgsWithRegex(regex string) []string { func getDefaultTapCommandArgsWithRegex(regex string) []string {
tapCommand := "tap" tapCommand := "tap"
defaultCmdArgs := getDefaultCommandArgs() defaultCmdArgs := getDefaultCommandArgs()
@ -324,16 +319,6 @@ func getLogsPath() (string, error) {
return logsPath, nil return logsPath, nil
} }
func daemonCleanup(t *testing.T, viewCmd *exec.Cmd) {
if err := runMizuClean(); err != nil {
t.Logf("error running mizu clean: %v", err)
}
if err := cleanupCommand(viewCmd); err != nil {
t.Logf("failed to cleanup view command, err: %v", err)
}
}
// waitTimeout waits for the waitgroup for the specified max timeout. // waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out. // Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {

View File

@ -22,6 +22,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"plugin" "plugin"
"regexp"
"sort" "sort"
"syscall" "syscall"
"time" "time"
@ -131,10 +132,6 @@ func main() {
} }
} }
if config.Config.SyncTappers {
startSyncingTappers()
}
hostApi(outputItemsChannel) hostApi(outputItemsChannel)
} else if *harsReaderMode { } else if *harsReaderMode {
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000) outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
@ -451,33 +448,19 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
} }
} }
func startSyncingTappers() { func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, istio bool) (*kubernetes.MizuTapperSyncer, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kubernetesProvider, err := kubernetes.NewProviderInCluster()
if err != nil {
logger.Log.Fatalf("error creating k8s provider: %+v", err)
}
if _, err := startMizuTapperSyncer(ctx, kubernetesProvider); err != nil {
logger.Log.Fatalf("error initializing tapper syncer: %+v", err)
}
}
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider) (*kubernetes.MizuTapperSyncer, error) {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{ tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: config.Config.TargetNamespaces, TargetNamespaces: targetNamespaces,
PodFilterRegex: config.Config.TapTargetRegex.Regexp, PodFilterRegex: podFilterRegex,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace, MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentImage: config.Config.AgentImage, AgentImage: config.Config.AgentImage,
TapperResources: config.Config.TapperResources, TapperResources: config.Config.TapperResources,
ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy), ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy),
LogLevel: config.Config.LogLevel, LogLevel: config.Config.LogLevel,
IgnoredUserAgents: config.Config.IgnoredUserAgents, IgnoredUserAgents: ignoredUserAgents,
MizuApiFilteringOptions: config.Config.MizuApiFilteringOptions, MizuApiFilteringOptions: mizuApiFilteringOptions,
MizuServiceAccountExists: true, //assume service account exists since daemon mode will not function without it anyway MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway
Istio: config.Config.Istio, Istio: istio,
}, time.Now()) }, time.Now())
if err != nil { if err != nil {

View File

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap/api"
"io/ioutil" "io/ioutil"
"os" "os"
) )
@ -12,7 +11,6 @@ import (
// these values are used when the config.json file is not present // these values are used when the config.json file is not present
const ( const (
defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000 defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000
defaultRegexTarget string = ".*"
DefaultDatabasePath string = "./entries" DefaultDatabasePath string = "./entries"
) )
@ -48,14 +46,8 @@ func applyDefaultConfig() error {
} }
func getDefaultConfig() (*shared.MizuAgentConfig, error) { func getDefaultConfig() (*shared.MizuAgentConfig, error) {
regex, err := api.CompileRegexToSerializableRegexp(defaultRegexTarget)
if err != nil {
return nil, err
}
return &shared.MizuAgentConfig{ return &shared.MizuAgentConfig{
TapTargetRegex: *regex,
MaxDBSizeBytes: defaultMaxDatabaseSizeBytes, MaxDBSizeBytes: defaultMaxDatabaseSizeBytes,
AgentDatabasePath: DefaultDatabasePath, AgentDatabasePath: DefaultDatabasePath,
SyncTappers: false,
}, nil }, nil
} }

View File

@ -2,12 +2,10 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger" "github.com/up9inc/mizu/shared/logger"
"mizuserver/pkg/api" "mizuserver/pkg/api"
"mizuserver/pkg/config"
"mizuserver/pkg/holder" "mizuserver/pkg/holder"
"mizuserver/pkg/providers" "mizuserver/pkg/providers"
"mizuserver/pkg/up9" "mizuserver/pkg/up9"
@ -17,13 +15,6 @@ import (
) )
func HealthCheck(c *gin.Context) { func HealthCheck(c *gin.Context) {
if config.Config.SyncTappers {
if providers.ExpectedTapperAmount != providers.TappersCount {
c.JSON(http.StatusInternalServerError, fmt.Sprintf("expecting more tappers than are actually connected (%d expected, %d connected)", providers.ExpectedTapperAmount, providers.TappersCount))
return
}
}
tappers := make([]shared.TapperStatus, 0) tappers := make([]shared.TapperStatus, 0)
for _, value := range providers.TappersStatus { for _, value := range providers.TappersStatus {
tappers = append(tappers, value) tappers = append(tappers, value)

View File

@ -20,7 +20,7 @@ var (
TappersStatus map[string]shared.TapperStatus TappersStatus map[string]shared.TapperStatus
authStatus *models.AuthStatus authStatus *models.AuthStatus
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime) RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
ExpectedTapperAmount = -1 //only relevant in daemon mode as cli manages tappers otherwise ExpectedTapperAmount = -1 //only relevant in install mode as cli manages tappers otherwise
tappersCountLock = sync.Mutex{} tappersCountLock = sync.Mutex{}
) )

21
cli/cmd/install.go Normal file
View File

@ -0,0 +1,21 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/telemetry"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "Installs mizu components",
RunE: func(cmd *cobra.Command, args []string) error {
go telemetry.ReportRun("install", nil)
runMizuInstall()
return nil
},
}
func init() {
rootCmd.AddCommand(installCmd)
}

71
cli/cmd/installRunner.go Normal file
View File

@ -0,0 +1,71 @@
package cmd
import (
"context"
"errors"
"fmt"
"github.com/creasty/defaults"
"github.com/up9inc/mizu/cli/config"
"github.com/up9inc/mizu/cli/errormessage"
"github.com/up9inc/mizu/cli/resources"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func runMizuInstall() {
kubernetesProvider, err := getKubernetesProviderForCli()
if err != nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel will be called when this function exits
var serializedValidationRules string
var serializedContract string
var defaultMaxEntriesDBSizeBytes int64 = 200 * 1000 * 1000
defaultResources := shared.Resources{}
defaults.Set(&defaultResources)
mizuAgentConfig := getInstallMizuAgentConfig(defaultMaxEntriesDBSizeBytes, defaultResources)
serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err)))
return
}
if err = resources.CreateInstallMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, nil, defaultMaxEntriesDBSizeBytes, defaultResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), false); err != nil {
var statusError *k8serrors.StatusError
if errors.As(err, &statusError) {
if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists {
logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance")
}
} else {
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
}
return
}
logger.Log.Infof(uiUtils.Magenta, "Created Mizu Agent components, run `mizu view` to connect to the mizu daemon instance")
}
func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Resources) *shared.MizuAgentConfig {
mizuAgentConfig := shared.MizuAgentConfig{
MaxDBSizeBytes: maxDBSizeBytes,
AgentImage: config.Config.AgentImage,
PullPolicy: config.Config.ImagePullPolicyStr,
LogLevel: config.Config.LogLevel(),
TapperResources: tapperResources,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentDatabasePath: shared.DataDirPath,
}
return &mizuAgentConfig
}

View File

@ -119,6 +119,5 @@ func init() {
tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)") tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules") tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts") tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts")
tapCmd.Flags().Bool(configStructs.DaemonModeTapName, defaultTapConfig.DaemonMode, "Run mizu in daemon mode, detached from the cli")
tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls") tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls")
} }

View File

@ -44,13 +44,9 @@ var apiProvider *apiserver.Provider
func RunMizuTap() { func RunMizuTap() {
state.startTime = time.Now() state.startTime = time.Now()
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
apiProvider = apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout) apiProvider = apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
return
}
var err error
var serializedValidationRules string var serializedValidationRules string
if config.Config.Tap.EnforcePolicyFile != "" { if config.Config.Tap.EnforcePolicyFile != "" {
serializedValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile) serializedValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile)
@ -94,12 +90,7 @@ func RunMizuTap() {
state.targetNamespaces = getNamespaces(kubernetesProvider) state.targetNamespaces = getNamespaces(kubernetesProvider)
mizuAgentConfig, err := getMizuAgentConfig(state.targetNamespaces, mizuApiFilteringOptions) mizuAgentConfig := getTapMizuAgentConfig()
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting mizu config: %v", errormessage.FormatError(err)))
return
}
serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig) serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig)
if err != nil { if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err))) logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err)))
@ -132,68 +123,41 @@ func RunMizuTap() {
} }
logger.Log.Infof("Waiting for Mizu Agent to start...") logger.Log.Infof("Waiting for Mizu Agent to start...")
if state.mizuServiceAccountExists, err = resources.CreateMizuResources(ctx, cancel, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.Tap.DaemonMode, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), config.Config.Tap.NoPersistentVolumeClaim); err != nil { if state.mizuServiceAccountExists, err = resources.CreateTapMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel()); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
var statusError *k8serrors.StatusError var statusError *k8serrors.StatusError
if errors.As(err, &statusError) { if errors.As(err, &statusError) {
if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists {
logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance") logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance")
} }
} else {
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
} }
return return
} }
if config.Config.Tap.DaemonMode {
if err := handleDaemonModePostCreation(cancel, kubernetesProvider); err != nil {
defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
cancel()
} else {
logger.Log.Infof(uiUtils.Magenta, "Mizu is now running in daemon mode, run `mizu view` to connect to the mizu daemon instance")
}
} else {
defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel) defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
// block until exit signal or error go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel)
utils.WaitForFinish(ctx, cancel) go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
}
// block until exit signal or error
utils.WaitForFinish(ctx, cancel)
} }
func handleDaemonModePostCreation(cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) error { func getTapMizuAgentConfig() *shared.MizuAgentConfig {
apiProvider := apiserver.NewProvider(GetApiServerUrl(), 90, 1*time.Second)
if err := waitForDaemonModeToBeReady(cancel, kubernetesProvider, apiProvider); err != nil {
return err
}
return nil
}
func getMizuAgentConfig(targetNamespaces []string, mizuApiFilteringOptions *api.TrafficFilteringOptions) (*shared.MizuAgentConfig, error) {
serializableRegex, err := api.CompileRegexToSerializableRegexp(config.Config.Tap.PodRegexStr)
if err != nil {
return nil, err
}
mizuAgentConfig := shared.MizuAgentConfig{ mizuAgentConfig := shared.MizuAgentConfig{
TapTargetRegex: *serializableRegex, MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(),
MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(), AgentImage: config.Config.AgentImage,
TargetNamespaces: targetNamespaces, PullPolicy: config.Config.ImagePullPolicyStr,
AgentImage: config.Config.AgentImage, LogLevel: config.Config.LogLevel(),
PullPolicy: config.Config.ImagePullPolicyStr, TapperResources: config.Config.Tap.TapperResources,
LogLevel: config.Config.LogLevel(), MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents, AgentDatabasePath: shared.DataDirPath,
TapperResources: config.Config.Tap.TapperResources,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
MizuApiFilteringOptions: *mizuApiFilteringOptions,
AgentDatabasePath: shared.DataDirPath,
Istio: config.Config.Tap.Istio,
SyncTappers: config.Config.Tap.DaemonMode,
} }
return &mizuAgentConfig, nil return &mizuAgentConfig
} }
/* /*
@ -215,17 +179,6 @@ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes.
} }
} }
func waitForDaemonModeToBeReady(cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, apiProvider *apiserver.Provider) error {
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
// TODO: TRA-3903 add a smarter test to see that tapping/pod watching is functioning properly
if err := apiProvider.TestConnection(); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Mizu was not ready in time, for more info check logs at %s", fsUtils.GetLogFilePath()))
return err
}
return nil
}
func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, mizuApiFilteringOptions api.TrafficFilteringOptions, startTime time.Time) error { func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, mizuApiFilteringOptions api.TrafficFilteringOptions, startTime time.Time) error {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{ tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: targetNamespaces, TargetNamespaces: targetNamespaces,

View File

@ -3,8 +3,6 @@ package configStructs
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared/logger"
"regexp" "regexp"
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
@ -24,7 +22,6 @@ const (
WorkspaceTapName = "workspace" WorkspaceTapName = "workspace"
EnforcePolicyFile = "traffic-validation-file" EnforcePolicyFile = "traffic-validation-file"
ContractFile = "contract" ContractFile = "contract"
DaemonModeTapName = "daemon"
IstioName = "istio" IstioName = "istio"
) )
@ -47,8 +44,6 @@ type TapConfig struct {
AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"` AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"`
ApiServerResources shared.Resources `yaml:"api-server-resources"` ApiServerResources shared.Resources `yaml:"api-server-resources"`
TapperResources shared.Resources `yaml:"tapper-resources"` TapperResources shared.Resources `yaml:"tapper-resources"`
DaemonMode bool `yaml:"daemon" default:"false"`
NoPersistentVolumeClaim bool `yaml:"no-persistent-volume-claim" default:"false"`
Istio bool `yaml:"istio" default:"false"` Istio bool `yaml:"istio" default:"false"`
} }
@ -84,9 +79,5 @@ func (config *TapConfig) Validate() error {
return errors.New(fmt.Sprintf("Can't run with both --%s and --%s flags", AnalysisTapName, WorkspaceTapName)) return errors.New(fmt.Sprintf("Can't run with both --%s and --%s flags", AnalysisTapName, WorkspaceTapName))
} }
if config.NoPersistentVolumeClaim && !config.DaemonMode {
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("the --set tap.no-persistent-volume-claim=true flag has no effect without the --%s flag, the claim will not be created anyway.", DaemonModeTapName))
}
return nil return nil
} }

View File

@ -6,13 +6,13 @@ import (
) )
var ( var (
SemVer = "0.0.1" SemVer = "0.0.1"
Branch = "develop" Branch = "develop"
GitCommitHash = "" // this var is overridden using ldflags in makefile when building GitCommitHash = "" // this var is overridden using ldflags in makefile when building
BuildTimestamp = "" // this var is overridden using ldflags in makefile when building BuildTimestamp = "" // this var is overridden using ldflags in makefile when building
RBACVersion = "v1" RBACVersion = "v1"
Platform = "" Platform = ""
DaemonModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb InstallModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb
) )
const DEVENVVAR = "MIZU_DISABLE_TELEMTRY" const DEVENVVAR = "MIZU_DISABLE_TELEMTRY"

View File

@ -14,7 +14,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
) )
func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, isInstallMode bool, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) (bool, error) { func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level) (bool, error) {
if !isNsRestrictedMode { if !isNsRestrictedMode {
if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil { if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil {
return false, err return false, err
@ -25,11 +25,9 @@ func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kuberne
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v", errormessage.FormatError(err))) logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v", errormessage.FormatError(err)))
} }
mizuServiceAccountExists, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace, isInstallMode) mizuServiceAccountExists, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
if err != nil { if err != nil {
if !isInstallMode { logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
}
} }
var serviceAccountName string var serviceAccountName string
@ -52,29 +50,70 @@ func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kuberne
LogLevel: logLevel, LogLevel: logLevel,
} }
if isInstallMode { if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil {
if !mizuServiceAccountExists { return mizuServiceAccountExists, err
defer CleanUpMizuResources(ctx, cancel, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
logger.Log.Fatalf(uiUtils.Red, fmt.Sprintf("Failed to ensure the resources required for mizu to run in daemon mode. cannot proceed. error: %v", errormessage.FormatError(err)))
}
if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts, noPersistentVolumeClaim); err != nil {
return mizuServiceAccountExists, err
}
} else {
if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil {
return mizuServiceAccountExists, err
}
} }
_, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName) _, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err != nil { if err != nil {
return mizuServiceAccountExists, err return mizuServiceAccountExists, err
} }
logger.Log.Debugf("Successfully created service: %s", kubernetes.ApiServerPodName) logger.Log.Debugf("Successfully created service: %s", kubernetes.ApiServerPodName)
return mizuServiceAccountExists, nil return mizuServiceAccountExists, nil
} }
func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) error {
if !isNsRestrictedMode {
if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil {
return err
}
logger.Log.Infof("Created mizu namespace")
}
if err := createMizuConfigmap(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, mizuResourcesNamespace); err != nil {
return err
}
logger.Log.Infof("Created config map")
_, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
if err != nil {
return err
}
if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil {
return err
}
logger.Log.Infof("Created RBAC")
serviceAccountName := kubernetes.ServiceAccountName
opts := &kubernetes.ApiServerOptions{
Namespace: mizuResourcesNamespace,
PodName: kubernetes.ApiServerPodName,
PodImage: agentImage,
ServiceAccountName: serviceAccountName,
IsNamespaceRestricted: isNsRestrictedMode,
SyncEntriesConfig: syncEntriesConfig,
MaxEntriesDBSizeBytes: maxEntriesDBSizeBytes,
Resources: apiServerResources,
ImagePullPolicy: imagePullPolicy,
LogLevel: logLevel,
}
if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts, noPersistentVolumeClaim); err != nil {
return err
}
logger.Log.Infof("Created Api Server deployment")
_, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err != nil {
return err
}
logger.Log.Infof("Created Api Server service")
return nil
}
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) error { func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) error {
_, err := kubernetesProvider.CreateNamespace(ctx, mizuResourcesNamespace) _, err := kubernetesProvider.CreateNamespace(ctx, mizuResourcesNamespace)
return err return err
@ -85,7 +124,7 @@ func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Pro
return err return err
} }
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string, isInstallMode bool) (bool, error) { func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string) (bool, error) {
if !isNsRestrictedMode { if !isNsRestrictedMode {
if err := kubernetesProvider.CreateMizuRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.ClusterRoleName, kubernetes.ClusterRoleBindingName, mizu.RBACVersion); err != nil { if err := kubernetesProvider.CreateMizuRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.ClusterRoleName, kubernetes.ClusterRoleBindingName, mizu.RBACVersion); err != nil {
return false, err return false, err
@ -95,11 +134,7 @@ func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.P
return false, err return false, err
} }
} }
if isInstallMode {
if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
}
return true, nil return true, nil
} }
@ -141,7 +176,7 @@ func tryToCreatePersistentVolumeClaim(ctx context.Context, kubernetesProvider *k
return false return false
} }
if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, opts.Namespace, kubernetes.PersistentVolumeClaimName, opts.MaxEntriesDBSizeBytes + mizu.DaemonModePersistentVolumeSizeBufferBytes); err != nil { if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, opts.Namespace, kubernetes.PersistentVolumeClaimName, opts.MaxEntriesDBSizeBytes + mizu.InstallModePersistentVolumeSizeBufferBytes); err != nil {
logger.Log.Warningf(uiUtils.Yellow, "An error has occured while creating a persistent volume claim for mizu, this means mizu data will be lost on mizu-api-server pod restart") logger.Log.Warningf(uiUtils.Yellow, "An error has occured while creating a persistent volume claim for mizu, this means mizu data will be lost on mizu-api-server pod restart")
logger.Log.Debugf("error creating persistent volume claim: %v", err) logger.Log.Debugf("error creating persistent volume claim: %v", err)
return false return false

View File

@ -38,8 +38,6 @@ Please make sure to use full option name (`tap.dry-run` as opposed to `dry-run`
* `all-namespaces` - special flag indicating whether Mizu should search and tap pods, matching the regex, in all namespaces. Default is `false`. Please use with caution, tapping too many pods can affect resource consumption. * `all-namespaces` - special flag indicating whether Mizu should search and tap pods, matching the regex, in all namespaces. Default is `false`. Please use with caution, tapping too many pods can affect resource consumption.
* `daemon` - instructs Mizu whether to run daemon mode (where CLI command exits after launch, and tapper & api-server pods in Kubernetes continue to run without controlling CLI). Typically supplied as command-line option `--daemon`. Default valie is `false`
* `dry-run` - if true, Mizu will print list of pods matching the supplied (or default) regex and exit without actually tapping the traffic. Default value is `false`. Typically supplied as command-line option `--dry-run` * `dry-run` - if true, Mizu will print list of pods matching the supplied (or default) regex and exit without actually tapping the traffic. Default value is `false`. Typically supplied as command-line option `--dry-run`
* `proxy-host` - IP address on which proxy to Mizu API service is launched; should be accessible at `proxy-host:gui-port`. Default value is `127.0.0.1` * `proxy-host` - IP address on which proxy to Mizu API service is launched; should be accessible at `proxy-host:gui-port`. Default value is `127.0.0.1`

View File

@ -1,22 +1,16 @@
# Mizu daemon mode # Mizu install standalone
Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run
indefinitely in the cluster. indefinitely in the cluster.
Please note that daemon mode requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md) Please note that install standalone requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md)
doc for more details. doc for more details.
```bash ```bash
$ mizu tap "^ca.*" --daemon $ mizu install
Mizu will store up to 200MB of traffic, old traffic will be cleared once the limit is reached.
Tapping pods in namespaces "sock-shop"
Waiting for mizu to be ready... (may take a few minutes)
+carts-66c77f5fbb-fq65r
+catalogue-5f4cb7cf5-7zrmn
..
``` ```
## Stop mizu daemon ## Stop mizu install
To stop the detached mizu instance and clean all cluster side resources, run `mizu clean` To stop the detached mizu instance and clean all cluster side resources, run `mizu clean`

View File

@ -57,11 +57,11 @@ Mizu needs following permissions on your Kubernetes cluster to run properly
- get - get
``` ```
## Permissions required running with --daemon flag or (optional) for service / pod name resolving ## Permissions required running with install command or (optional) for service / pod name resolving
Mandatory permissions for running with `--daemon` flag. Mandatory permissions for running with install command.
Optional for service/pod name resolving in non daemon mode Optional for service/pod name resolving in non install standalone
```yaml ```yaml
- apiGroups: - apiGroups:

View File

@ -6,7 +6,6 @@ import (
"github.com/op/go-logging" "github.com/op/go-logging"
"github.com/up9inc/mizu/shared/logger" "github.com/up9inc/mizu/shared/logger"
"github.com/up9inc/mizu/tap/api"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -34,19 +33,13 @@ type Resources struct {
} }
type MizuAgentConfig struct { type MizuAgentConfig struct {
TapTargetRegex api.SerializableRegexp `json:"tapTargetRegex"` MaxDBSizeBytes int64 `json:"maxDBSizeBytes"`
MaxDBSizeBytes int64 `json:"maxDBSizeBytes"` AgentImage string `json:"agentImage"`
TargetNamespaces []string `json:"targetNamespaces"` PullPolicy string `json:"pullPolicy"`
AgentImage string `json:"agentImage"` LogLevel logging.Level `json:"logLevel"`
PullPolicy string `json:"pullPolicy"` TapperResources Resources `json:"tapperResources"`
LogLevel logging.Level `json:"logLevel"` MizuResourcesNamespace string `json:"mizuResourceNamespace"`
IgnoredUserAgents []string `json:"ignoredUserAgents"` AgentDatabasePath string `json:"agentDatabasePath"`
TapperResources Resources `json:"tapperResources"`
MizuResourcesNamespace string `json:"mizuResourceNamespace"`
MizuApiFilteringOptions api.TrafficFilteringOptions `json:"mizuApiFilteringOptions"`
AgentDatabasePath string `json:"agentDatabasePath"`
Istio bool `json:"istio"`
SyncTappers bool `json:"syncTappers"`
} }
type WebSocketMessageMetadata struct { type WebSocketMessageMetadata struct {