728x90
A problem occurred configuring root project 'moyamo-android'.
> Could not resolve all files for configuration ':classpath'.
   > Could not resolve androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0.
     Required by:
         project :
      > No matching variant of androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.1.1' but:
          - Variant 'apiElements' capability androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.1.1')
          - Variant 'runtimeElements' capability androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.1.1')
          - Variant 'sourcesElements' capability androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.1.1')

* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'moyamo-android'.
	at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:84)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:77)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.access$400(LifecycleProjectEvaluator.java:55)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:111)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:395)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$fromMutableState$2(DefaultProjectStateRegistry.java:418)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withReplacedLocks(DefaultWorkerLeaseService.java:345)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:418)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:394)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:100)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:72)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:779)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:156)
	at org.gradle.api.internal.project.ProjectLifecycleController.lambda$ensureSelfConfigured$2(ProjectLifecycleController.java:84)
	at org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)
	at org.gradle.internal.model.StateTransitionController.lambda$maybeTransitionIfNotCurrentlyTransitioning$10(StateTransitionController.java:199)
	at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:34)
	at org.gradle.internal.model.StateTransitionController.maybeTransitionIfNotCurrentlyTransitioning(StateTransitionController.java:195)
	at org.gradle.api.internal.project.ProjectLifecycleController.ensureSelfConfigured(ProjectLifecycleController.java:84)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.ensureConfigured(DefaultProjectStateRegistry.java:369)
	at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:33)
	at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:47)
	at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:42)
	at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:65)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)
	at org.gradle.initialization.VintageBuildModelController.lambda$prepareProjects$2(VintageBuildModelController.java:84)
	at org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)
	at org.gradle.internal.model.StateTransitionController.lambda$transitionIfNotPreviously$11(StateTransitionController.java:213)
	at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:34)
	at org.gradle.internal.model.StateTransitionController.transitionIfNotPreviously(StateTransitionController.java:209)
	at org.gradle.initialization.VintageBuildModelController.prepareProjects(VintageBuildModelController.java:84)
	at org.gradle.initialization.VintageBuildModelController.getConfiguredModel(VintageBuildModelController.java:64)
	at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$withProjectsConfigured$1(DefaultBuildLifecycleController.java:122)
	at org.gradle.internal.model.StateTransitionController.lambda$notInState$3(StateTransitionController.java:132)
	at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
	at org.gradle.internal.model.StateTransitionController.notInState(StateTransitionController.java:128)
	at org.gradle.internal.build.DefaultBuildLifecycleController.withProjectsConfigured(DefaultBuildLifecycleController.java:122)
	at org.gradle.internal.build.DefaultBuildToolingModelController.locateBuilderForTarget(DefaultBuildToolingModelController.java:57)
	at org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.lambda$locateBuilderForTarget$0(DefaultBuildTreeModelCreator.java:73)
	at org.gradle.internal.build.DefaultBuildLifecycleController.withToolingModels(DefaultBuildLifecycleController.java:215)
	at org.gradle.internal.build.AbstractBuildState.withToolingModels(AbstractBuildState.java:140)
	at org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.locateBuilderForTarget(DefaultBuildTreeModelCreator.java:73)
	at org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.locateBuilderForDefaultTarget(DefaultBuildTreeModelCreator.java:68)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getTarget(DefaultBuildController.java:157)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getModel(DefaultBuildController.java:101)
	at org.gradle.tooling.internal.consumer.connection.ParameterAwareBuildControllerAdapter.getModel(ParameterAwareBuildControllerAdapter.java:39)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.getModel(UnparameterizedBuildController.java:113)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.getModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:97)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:81)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:66)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:116)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:42)
	at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionAdapter.runAction(AbstractClientProvidedBuildActionRunner.java:131)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionAdapter.beforeTasks(AbstractClientProvidedBuildActionRunner.java:99)
	at org.gradle.internal.buildtree.DefaultBuildTreeModelCreator.beforeTasks(DefaultBuildTreeModelCreator.java:52)
	at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$fromBuildModel$2(DefaultBuildTreeLifecycleController.java:74)
	at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$runBuild$4(DefaultBuildTreeLifecycleController.java:98)
	at org.gradle.internal.model.StateTransitionController.lambda$transition$6(StateTransitionController.java:169)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)
	at org.gradle.internal.model.StateTransitionController.lambda$transition$7(StateTransitionController.java:169)
	at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
	at org.gradle.internal.model.StateTransitionController.transition(StateTransitionController.java:169)
	at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.runBuild(DefaultBuildTreeLifecycleController.java:95)
	at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.fromBuildModel(DefaultBuildTreeLifecycleController.java:73)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:43)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:53)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.internal.buildtree.ProblemReportingBuildActionRunner.run(ProblemReportingBuildActionRunner.java:49)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:65)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:140)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.lambda$execute$0(RootBuildLifecycleBuildActionExecutor.java:40)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:122)
	at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.execute(RootBuildLifecycleBuildActionExecutor.java:40)
	at org.gradle.internal.buildtree.InitDeprecationLoggingActionExecutor.execute(InitDeprecationLoggingActionExecutor.java:58)
	at org.gradle.internal.buildtree.DefaultBuildTreeContext.execute(DefaultBuildTreeContext.java:40)
	at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.lambda$execute$0(BuildTreeLifecycleBuildActionExecutor.java:65)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
	at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.execute(BuildTreeLifecycleBuildActionExecutor.java:65)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:61)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:57)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor.execute(RunAsBuildOperationBuildActionExecutor.java:57)
	at org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.lambda$execute$0(RunAsWorkerThreadBuildActionExecutor.java:36)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:249)
	at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:109)
	at org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.execute(RunAsWorkerThreadBuildActionExecutor.java:36)
	at org.gradle.tooling.internal.provider.continuous.ContinuousBuildActionExecutor.execute(ContinuousBuildActionExecutor.java:110)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecutor.execute(SubscribableBuildActionExecutor.java:64)
	at org.gradle.internal.session.DefaultBuildSessionContext.execute(DefaultBuildSessionContext.java:46)
	at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter$ActionImpl.apply(BuildSessionLifecycleBuildActionExecuter.java:100)
	at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter$ActionImpl.apply(BuildSessionLifecycleBuildActionExecuter.java:88)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:69)
	at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:62)
	at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:41)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:32)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:38)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.internal.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:64)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:49)
Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':classpath'.
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.mapFailure(DefaultConfiguration.java:1716)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$3400(DefaultConfiguration.java:177)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.mapFailure(DefaultConfiguration.java:2443)
	at org.gradle.api.internal.artifacts.configurations.ResolutionHost.rethrowFailure(ResolutionHost.java:30)
	at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.visitContents(ResolutionBackedFileCollection.java:74)
	at org.gradle.api.internal.file.AbstractFileCollection.getFiles(AbstractFileCollection.java:130)
	at org.gradle.api.internal.file.AbstractFileCollection.iterator(AbstractFileCollection.java:171)
	at org.gradle.internal.classpath.DefaultClassPath.of(DefaultClassPath.java:56)
	at org.gradle.api.internal.initialization.DefaultScriptClassPathResolver.resolveClassPath(DefaultScriptClassPathResolver.java:86)
	at org.gradle.api.internal.initialization.DefaultScriptHandler.getInstrumentedScriptClassPath(DefaultScriptHandler.java:93)
	at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.defineScriptHandlerClassScope(DefaultPluginRequestApplicator.java:176)
	at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.applyPlugins(DefaultPluginRequestApplicator.java:90)
	at org.gradle.kotlin.dsl.provider.PluginRequestsHandler.handle(PluginRequestsHandler.kt:48)
	at org.gradle.kotlin.dsl.provider.StandardKotlinScriptEvaluator$InterpreterHost.applyPluginsTo(KotlinScriptEvaluator.kt:212)
	at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.applyPluginsTo(Interpreter.kt:382)
	at Program.execute(Unknown Source)
	at org.gradle.kotlin.dsl.execution.Interpreter$ProgramHost.eval(Interpreter.kt:512)
	at org.gradle.kotlin.dsl.execution.Interpreter.eval(Interpreter.kt:210)
	at org.gradle.kotlin.dsl.provider.StandardKotlinScriptEvaluator.evaluate(KotlinScriptEvaluator.kt:119)
	at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory$create$1.invoke(KotlinScriptPluginFactory.kt:51)
	at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory$create$1.invoke(KotlinScriptPluginFactory.kt:48)
	at org.gradle.kotlin.dsl.provider.KotlinScriptPlugin.apply(KotlinScriptPlugin.kt:35)
	at org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:65)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.configuration.BuildOperationScriptPlugin.lambda$apply$0(BuildOperationScriptPlugin.java:62)
	at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
	at org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:62)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:395)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:413)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:394)
	at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:46)
	at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:27)
	at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:109)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:395)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$fromMutableState$2(DefaultProjectStateRegistry.java:418)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withReplacedLocks(DefaultWorkerLeaseService.java:345)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:418)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:394)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:100)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:72)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:779)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:156)
	at org.gradle.api.internal.project.ProjectLifecycleController.lambda$ensureSelfConfigured$2(ProjectLifecycleController.java:84)
	at org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)
	at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)
	at org.gradle.internal.model.StateTransitionController.lambda$maybeTransitionIfNotCurrentlyTransitioning$10(StateTransitionController.java:199)
	at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:34)
	at org.gradle.internal.model.StateTransitionController.maybeTransitionIfNotCurrentlyTransitioning(StateTransitionController.java:195)
	at org.gradle.api.internal.project.ProjectLifecycleController.ensureSelfConfigured(ProjectLifecycleController.java:84)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.ensureConfigured(DefaultProjectStateRegistry.java:369)
	at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:33)
	at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:47)
	at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:42)
	at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:65)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)

 

Yagnesh가 친절하게 답변을 달아줬다..

 

Yagnesh Tatamiya 
3 days ago
 

which version of android studio you are using?

it’s working fine with “Android Studio Flamingo | 2022.2.1 Patch 2”

try and update android studio.


그래서 이제 2021년도산 친구를 버리고 홍학친구로 갈아타줫다... 어근데 새로운 문제가 발생하더라고

Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':app:debugCompileClasspath'.

Android studio 3.2.1 ArtifactResolveException: Could not resolve all artifacts for configuration ':classpath'

 

Android studio 3.2.1 ArtifactResolveException: Could not resolve all artifacts for configuration ':classpath'

After I update Android Studio to 3.2.1 and gradle version in my project I am getting following build error. I have already checked lots of questions related this question but no luck. Project

stackoverflow.com

Android Gradle Sync failed: Could not resolve all artifacts for configuration ':classpath'

 

Android Gradle Sync failed: Could not resolve all artifacts for configuration ':classpath'

The error is the following: Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all artifacts for configuration ':classpa...

stackoverflow.com

 

일단 토글모드 Offline 으로 만든다음에 Invalidate Cache 를 해서 캐시한번 비워주고 다시 시도해봤는데 작동이 안됫다.

 

그래서 일단 Build 를 다시 시도하는데 Gradle Sync 하는 과정에서 Gradle 필요하다그래서 다시 켜줫다.

작동 성공..

 

 

728x90

by Hyeong Sik Yang | on 20 6월 2023 | in Amazon Aurora, Amazon Route 53, AWS Step Functions, Database | Permalink |  Share

 

케이타운포유는 급성장 중인 K-Pop 및 한류 상품을 글로벌 판매하는 이커머스 서비스를 제공하는 회사로 2002년 서비스를 시작한 이래 200개국, 6개 언어로 500만 회원, 5,000여 개 팬클럽을 대상으로 서비스하고 있습니다. 케이타운포유는 B2B에서 B2C로 전환 후 2021년 매출액 2,000억을 돌파하며 짧은 기간에 15배에 달하는 성장을 하였습니다.

케이타운포유는 사용자서비스, 운영시스템, 물류시스템(WMS) 등의 시스템을 개발, 운영하고 있으며, 이를 위해 모든 시스템을 AWS 클라우드에서 구축/운영하고 있습니다. 2022년에 많은 버그를 해결함과 동시에 트래픽 수용을 위한 개선을 이뤘고, 2023년에는 상품구조 개선, 물류시스템 개선 및 고도화, 거대 모놀리식 레거시 시스템을 MSA 구조로 변경 진행 중에 있습니다.

순간적인 스파이크 트래픽에 따른 Aurora Auto Scaling 기능의 한계

케이타운포유는 이벤트 발생시 평소의 수배에서 수십배의 트래픽을 처리하고 있습니다. 어플리케이션에서 Amazon Elasticache를 적극 활용하여 서비스 안정성과 지연을 최소화하고 있지만 실시간 데이터베이스 쿼리를 요구하는 많은 API들로 인하여 급격한 데이터베이스 부하가 발생하고 있습니다. 예측 불가능한 이벤트로 발생한 데이터베이스 부하를 해소하기 위해 케이타운포유는 Aurora Cluster의 오토스케일링을 적극 활용하고 있습니다.

이벤트 발생시 Aurora 오토스케일링을 활용한 자동 Scale-out 또는 다수의 읽기 전용 인스턴스를 사전 증설을 하고 있음에도 아래와 같은 상황에 한계가 있습니다.

  • 예측 불가능한 순간적인 스파이크 트래픽 부하로 인한 서비스 장애 현상
  • Scale-out 발생시, 평균 15분 소요되는 읽기 전용 인스턴스 준비 시간
  • 비즈니스 특성상 최소 수 시간 전 읽기 전용 인스턴스 증설 비용
  • 예측 불가능한 이벤트 규모로 인한 과도한 대응과 리소스 낭비

이러한 비즈니스 요구사항과 예측 불가능한 트래픽의 대응 및 Aurora 오토스케일링 소요시간과 비용 문제를 해결하기 위해 개선된 오토스케일링 전략이 필요했습니다.

개선 아이디어

Aurora의 읽기 전용 인스턴스가 생성되는 약 15분 동안 트래픽을 여분의 인스턴스가 처리할 수 있다면 서비스 장애 문제를 해결할 수 있습니다. 읽기 전용 인스턴스가 생성되는 시간 동안에만 트래픽을 받고 평소에는 트래픽을 처리하지 않으며 트래픽을 처리한만큼만 요금을 지불하는 인스턴스가 있으면 이 문제를 효과적으로 개선할 수 있습니다. 그리고 이 구조는 이벤트 대응을 위해 사전에 증설하는 읽기 전용 인스턴스를 줄여 비용 최적화적으로도 도움이 될 것입니다. Cloudwatch에서 제공하는 Aurora RDS 메트릭의 해상도는 1분입니다. 순간적인 트래픽으로 인한 읽기 전용 인스턴스 부하의 빠른 감지와 빠른 오토스케일링을 위해 고해상도의 메트릭을 필요합니다.

이 아이디어를 정리해보면 필요한 인프라 변경요소는 다음과 같습니다.

  • 데이터베이스 부하 대응을 위한 고해상도 메트릭
  • 사용한 만큼 지불하는 여분의 인스턴스
  • 특정 임계치를 넘는 트래픽 발생시 여분의 인스턴스로 트래픽 분배하는 구조
  • 자동화된 운영 아키텍처

Amazon Aurora Hybrid(Mixed-Configuration) Cluster

Amazon Aurora DB Cluster는 다음과 같이 두 가지 유형의 DB 인스턴스로 구성됩니다.

  • 기본 DB 인스턴스 – 읽기 및 쓰기 작업을 지원하고, Cluster 볼륨의 모든 데이터 수정을 실행합니다.
  • Aurora 복제본(읽기 전용 인스턴스) – 기본 DB 인스턴스와 동일한 스토리지 볼륨에 연결되며 읽기 작업만 지원합니다.

Amazon Aurora 데이터베이스의 아키텍처 특징 중 하나는 컴퓨팅과 스토리지의 분리입니다. Amazon Aurora 스토리지는 데이터베이스의 데이터 양이 증가함에 따라 자동으로 크기가 조절됩니다.

Aurora Serverless v2는 CPU 및 메모리 리소스를 운영 중단 없이 자동적으로 크기가 조절됩니다. Aurora Serverless v2 용량에 대한 요금은 ACU 시간으로 측정됩니다. ACU는 약 2기가바이트(GB)의 메모리로 해당 CPU 및 네트워킹의 합한 용량입니다. 정의할 수 있는 Aurora Serverless v2 용량은 최소 0.5 ACU에서 최대 128 ACU입니다.

이러한 Amazon Aurora DB Cluster의 아키텍처로 특징으로 기존 프로비저닝 된 Aurora Cluster에 Aurora Serverless v2 읽기 전용 인스턴스를 추가하여, 아래 그림과 같이 Aurora Mixed-Configuration Cluster 형태로 구성 가능합니다. 케이타운포유는 이 데이터베이스 아키텍처를 Aurora Hybrid Cluster라고 부르고 있습니다.

커스텀 Aurora 오토스케일링 아키텍처

커스텀 Aurora 오토스케일링 아키텍처에서는 다음과 같은 로직으로 데이터베이스 부하를 처리합니다.

  • 기존 프로비저닝 된 Aurora Cluster에 추가된 Serverless v2 읽기 전용 인스턴스는 가중치가 0입니다. 가중치가 0이므로 평상시에는 트래픽이 발생하지 않아 Serverless v2 읽기 전용 인스턴스에 대한 요금이 부과되지 않습니다.
  • 만약, 기존 프로비저닝 된 Aurora Cluster의 읽기 전용 인스턴스의 평균 CPU 부하량이 임계치를 넘으면 알람을 발생합니다.
  • 알람이 발생되면, 가중치를 조절하여 추가된 Aurora Serverless v2 읽기 전용 인스턴스로 일부 트래픽을 전달합니다.
    • 가중치를 조절한 이후에도, 알람 상태를 지속적으로 확인하여 추가적인 트래픽 전환이 필요한지 판단합니다.
    • 만약, 알람이 해소되지 않고, 트래픽의 부하가 많은 상황이면, Serverless v2 읽기 전용 인스턴스가 더 많은 트래픽을 처리하도록 가중치를 추가 조절합니다.
  • 또한, 알람이 발생하였기 때문에, 기존 프로비저닝 된 Aurora Cluster에서 스케일 아웃이 발생하고, Provisioned 읽기 전용 인스턴스를 추가합니다.
  • 추가된 Provisioned 읽기 전용 인스턴스의 상태가 Available한 상태가 되면, 알람 상태를 확인하여 트래픽을 Provisioned 읽기 전용 인스턴스가 처리하도록 가중치를 조절합니다.
  • 최종적으로 알람이 해소된다면, serverless v2 읽기 전용 인스턴스의 가중치를 0으로 조절하여 사용을 중지하고, 요금을 발생하지 않습니다.

이러한 아이디어를 통해 효율적이고 안정적으로 데이터베이스를 사용할 수 있도록 아래와 같이 아키텍처를 변경하였습니다.

커스텀 Aurora 오토스케일링 아키텍처는 다음과 같이 크게 4개 영역으로 구분되어 동작합니다.

  • Aurora Serverless v2 읽기 전용 인스턴스 추가 및 커스텀 엔드포인트 설정
  • Enhanced Metric 지표 수집 및 Cloudwatch 커스텀 메트릭 생성
  • Step Functions을 이용한 Route53 가중치 기반 레코드의 가중치 조절
  • Cloudwatch 커스텀 메트릭을 이용한 Aurora Cluster 오토스케일링 설정

1. Aurora Serverless v2 읽기 전용 인스턴스 추가 및 커스텀 엔드포인트 설정

순간적인 스파이크 트래픽을 대응하기 위해 기존에 프로비저닝 된 Aurora Cluster에 Serverless v2 읽기 전용 인스턴스를 추가합니다. Serverless v2 읽기 전용 인스턴스를 최하위 우선순위로 설정해서 Fail-over 발생시, 쓰기 인스턴스로 승격되는 것을 방지할 수 있습니다. Serverless v2 읽기 전용 인스턴스를 여러 대 생성하면 더 급격한 스파이크 트래픽도 대응할 수 있습니다.

커스텀 엔드포인트는 아래와 같이 각각 설정합니다.

  • Provisioned 커스텀 엔드포인트
    • 평상시 트래픽을 처리할 기존 프로비저닝 된 Aurora Cluster에 속한 Provisioned된 읽기 전용 인스턴스 그룹을 가리키는 엔드포인트
  • Serverless v2 커스텀 엔드포인트
    • 임계치를 넘어가는 부하 트래픽을 처리하는 Serverless v2 읽기 전용 인스턴스를 가리키는 엔드포인트

이때, 오토스케일링으로 인해 추가되는 Provisioned 인스턴스는 Provisioned 커스텀 엔드포인트에만 추가되도록 설정합니다.

이렇게 생성된 각각의 커스텀 엔드포인트는 Route53에 Private Hosted Zone으로 도메인을 생성하고, 같은 서브 도메인으로 가중치 기반 라우팅 레코드((Weighted Based Record)를 각각 생성합니다. Serverless v2 커스텀 엔드포인트에 연결된 가중치 기반 레코드의 가중치 값은 0으로 설정합니다.

이 과정을 통해 설정된 DNS 주소로 감당 가능한 평상시의 트래픽은 Provisioned 커스텀 엔드포인트에 연결된 인스턴스가 처리하고, 임계치를 넘어가는 부하 트래픽은 Serverless v2 읽기 전용 인스턴스가 처리하여 최소한의 금액으로 서비스의 안정성을 높일 수 있습니다.

2. Enhanced Metric 지표 수집 및 커스텀 메트릭 생성

2.1. 고해상도 메트릭 수집

AWS Cloudwatch 통해 수집되는 Amazon Aurora의 기본 메트릭은 60초 단위로 수집됩니다. 순간적인 스파이크 트래픽에 따른 급격한 데이터베이스 CPU 사용률 변화를 감지하기위해 높은 해상도를 가진 메트릭의 수집이 필요합니다.

Enhanced Monitoring은 데이터베이스가 구동되는 OS의 메트릭을 최대 1초의 해상도로 모니터링 할 수 있습니다. Aurora Cluster의 인스턴스를 수정하여 Enhanced Monitoring을 활성화 합니다. 오토스케일링으로 생성되는 인스턴스는 기본 인스턴스의 설정을 공유하므로 기본 인스턴스의 Enhanced monitoring도 활성화 하여야 합니다.

Enhanced Monitoring 기능을 활성화 하면 RDSOSMetrics 라는 이름의 Cloudwatch Logs Groups에 해당 지표가 저장됩니다. 케이타운포유는 이 Log에서 추출한 지표를 통해 고해상도 메트릭을 구성하였습니다.

2.2 Subscription Filter를 이용한 메트릭 정제

Cloudwatch Logs Subscription Filter는 Cloudwatch에 Log로 수신되는 정보를 구독하여 Lambda, Kinesis 등의 서비스로 전송하는 기능입니다. RDSOSMetrics Log Group에는 Enhanced Monitoring 기능이 활성화된 모든 인스턴스들의 지표들이 수집되므로 subscription filter를 이용하여, 위에서 설정한 Provisioned 커스텀 엔드포인트의 대상이 되는 인스턴스 메트릭만을 정제하는 작업을 진행했습니다.

먼저, 아래 코드는 Lambda의 event로 전송된 압축된 로그를 읽을 수 있도록 처리하는 예시입니다.

import json
import base64
import gzip

def lambda_handler(event, context, metrics):
	data = event["awslogs"]["data"]
    decoded_data = base64.b64decode(data)
    decompressed_data = gzip.decompress(decoded_data)
    metric = json.loads(json.loads(decompressed_data)["logEvents"][0]["message"])
Python

다음으로 위에서 설정한 각 커스템 엔드포인트에 해당하는 읽기 전용 인스턴스의 지표만을 추출합니다. 하지만, 메트릭에서는 커스텀 엔드포인트를 기준으로 추출할 수 없습니다. 따라서, AWS boto3에서 제공하는 RDS의 describe_db_instances, describe_db_clusters API를 이용하여 available 상태인 읽기 전용 인스턴스의 CPU 사용량 메트릭만을 추출하였습니다.

instances_with_status = rds.describe_db_instances(
        Filters=[{"Name": "db-cluster-id", "Values": ["<CLUSTER_NAME>"]}]
    )["DBInstances"]

instances_with_primary = rds.describe_db_clusters(DBClusterIdentifier="<CLUSTER_NAME>")[
    "DBClusters"
][0]["DBClusterMembers"]

instances = []
for i in instances_with_status:
    for c in instances_with_primary:
        if i["DBInstanceIdentifier"] == c["DBInstanceIdentifier"]:
            instances.append(dict(i, **c))
            break

available_provisioned_read_replicas = [
    instance["DBInstanceIdentifier"]
    for instance in instances
    if instance["DBInstanceStatus"] in available_status
    and instance["DBInstanceClass"] != "db.serverless"
    and not instance["IsClusterWriter"]
Python

2.3 EMF를 이용한 실시간 메트릭 반영

빠른 메트릭 반영을 위해 EMF(Embedded Metric Format)을 이용합니다. EMF는 Cloudwatch에 맞춰진 구조화된 로그 포맷으로서, 메트릭 반영이 빠르다는 장점을 가지고 있으며 장애대응과 같은 실시간성 지표가 필요할때 사용할 수 있습니다.

Python을 런타임으로 사용한 Lambda의 경우 metric_scope decorator를 이용하여 쉽게 반영할 수 있습니다. 아래 코드는 Lambda에서 EMF를 이용해 Cloudwatch에 맞춰진 구조화된 로그 포맷으로 변환하는 예시입니다.

from aws_embedded_metrics import metric_scope
from aws_embedded_metrics.storage_resolution import StorageResolution

@metric_scope
def lambda_handler(event, context, metrics):
    #    ... metric data 추출 / 변환 ...
    metrics.set_dimensions("<METRIC_DIMENSION_NAME>": "<METRIC_DIMENSION_VALUE>"})
    metrics.put_metric("<METRIC_NAME>", "<METRIC_VALUE>", ageResolution.HIGH)
    metrics.set_namespace("<METRIC_NAMESPACE>")
Python

EMF를 사용하면 정제한 커스텀 메트릭 값(대상 인스턴스의 CPU 사용량)을 1초 이내에 Cloudwatch에서 확인할 수 있습니다.

3. Step Function을 이용한 Route53 레코드 가중치 조절

데이터베이스의 높은 부하 발생을 처리하는 알람 이벤트를 생성하고 트래픽 비율(Route53의 레코드 가중치)를 조절하는 Step Functions을 트리거 하여 프로비저닝 된 읽기 전용 인스턴스와 Serverless v2 읽기 전용 인스턴스 사이의 트래픽 비율을 조절하도록 구성하였습니다.

케이타운포유는 데이터베이스 부하 증가와 감소를 처리할 Step Functions을 분리하여 관리하였습니다. 아래는 부하 증가 시 발생되는 Step Functions을 기준으로 작성하였습니다.

3.1 읽기 전용 인스턴스 부하 알람 및 이벤트 트리거 구성

위에서 생성한 커스텀 메트릭을 이용하여 Cloudwatch Alarm을 생성합니다. 높은 데이터베이스 부하 알림 데이터 포인트는 적게, 낮은 부하 알림 데이터 포인트는 높게 설정하는 것으로 시스템 안정성을 높일 수 있습니다. AWS 네임스페이스 메트릭이 아닌 커스텀 메트릭을 사용할 경우, 알람의 데이터 포인트 주기를 최대 10초 까지 높일 수 있는 장점이 있습니다.

3.2 트래픽 비율 조절 Step Function 구성

Cloudwatch 알람 이벤트는 OK 상태에서 Alarm 또는 반대의 상태로 변경되는 최초 1회에서만 발생합니다. 스파이크 트래픽 이후에도 점진적으로 트래픽이 상승하는 경우에는 한번의 Route53 가중치 조절만으로는 안정성을 확보할 수 없습니다. 가중치 조절 이후, 데이터베이스의 상태를 지속적으로 점검하여 안정성을 확보하기 위해서 Step Functions을 사용합니다.

알람에 의한 Step Functions은 다음과 같이 동작합니다.

  • Route53에 Serverless v2 읽기 전용 인스턴스와 연결된 레코드의 가중치를 수정합니다.
  • 조절된 가중치가 실제 데이터베이스 사용량과 CloudWatch Alarm에 반영될 수 있도록 최소 Cloudwatch Alarm의 데이터 포인트 주기만큼 대기합니다.
  • Cloudwatch Alarm이 해결되었는지 확인합니다.
    • Alarm이 해결된 경우, Step Functions을 종료합니다.
    • Alarm이 해결되지 않은 경우, 다시 Route53 레코드의 가중치를 조절합니다.

AWS API를 사용하고 대기하는 등의 부분은 별도의 Lambda 없이 Step Function에 내장된 함수들을 사용하였습니다. 케이타운포유는 내장된 함수를 사용하여 Lambda 사용시간을 줄이고 비용을 최적화할 수 있었습니다.

4. 커스텀 메트릭을 이용한 Aurora 오토스케일링

Serverless v2 인스턴스의 CPU 사용량은 Max ACU 기준 사용중인 ACU로 계산됩니다. Aurora Cluster에서 제공하는 오토스케일링 기능은 아래와 같이 Aurora Cluster에 포함된 모든 읽기 전용 인스턴스의 평균 CPU 사용량을 기준으로 동작합니다.

이 계산방식으로 인해 Aurora Cluster에 포함된 모든 읽기 전용 인스턴스의 평균 CPU 사용량이 프로비저닝 된 읽기 전용 인스턴스들의 평균 CPU보다 낮게 계산되어, 원하는 CPU 기준에 오토스케일링이 발생하지 않는 문제가 있습니다.

따라서, 아래와 같이 프로비저닝 된 읽기 전용 인스턴스만으로 평균 CPU 사용량을 통해 실제 사용중인 리소스 기반으로 오토스케일링이 일어날 수 있도록 커스텀 메트릭에 기반한 오토스케일링을 구성하였습니다.

커스텀 메트릭을 이용하여 Aurora Cluster에 오토스케일링을 구성하기 위해 AWS 서비스 중 하나인 AWS Auto Scaling 서비스의 API를 사용하여야 합니다. AWS Auto Scaling 서비스는 DynamoDB, ECS, Aurora, EMR 등의 AWS 서비스의 오토스케일링 구현을 위한 서비스입니다. AWS Auto Scaling 서비스를 이용하면 Aurora Cluster에서 지원하는 오토스케일링 기준 지표인 “읽기 전용 인스턴스의 평균 CPU 사용량” 과 “읽기 전용 인스턴스의 평균 커넥션”이 아닌 별도의 메트릭을 통해 스케일링 정책을 구성할 수 있다는 장점이 있습니다.

AWS CLI를 사용하여 커스텀 메트릭을 사용하여 아래와 같이 각각 수행하여 Aurora Cluster에 오토스케일링을 구성하였습니다.

1. 오토스케일링에 사용할 대상을 등록합니다. 최소/최대 용량을 제어할 수 있습니다.

aws application-autoscaling register-scalable-target \
  --service-namespace rds \
  --scalable-dimension rds:cluster:ReadReplicaCount \
  --resource-id cluster:"<CLUSTER_NAME>" \
  --min-capacity "<MIN_CAPACITY>" \
  --max-capacity "<MAX_CAPACITY>"
Bash

2. 오토스케일링에 사용할 지표와 정책을 결정합니다.

aws application-autoscaling put-scaling-policy \
  --policy-name "<POLICY_NAME>" \
  --service-namespace rds \
  --resource-id cluster:"<CLUSTER_NAME>" \
  --scalable-dimension rds:cluster:ReadReplicaCount \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration file://metric.json
Bash

케이타운포유는 초 단위의 Custom Metric을 활용하여 오토스케일링이 빠르게 적용될 수 있도록 구성하였습니다. Policy Type은 TargetTrackingScaling 로 하여 일반적인 부하 상황 모두 처리할 수 있도록 Policy를 구성하였습니다. TargetTrackingScaling은 현재 CPU 사용량에 기반하여 적당한 수준의 Read Replica를 늘리는 정책입니다. 서비스 워크로드 특성에 따라 StepScaling을 이용하여 지정된 사이즈만큼 증가하거나 감소 하도록 정책을 설정할 수 있습니다.

metric.json은 위에서 생성한 고해상도의 커스텀 메트릭을 이용하여 구성하였습니다.

{
  "TargetValue": "<TARGET_VALUE>",
  "CustomizedMetricSpecification": {
    "MetricName": "<METRIC_NAME>",
    "Namespace": "<METRIC_NAMESPACE>",
    "Dimensions": [
      {
        "Name": "<DIMENSION_NAME>",
        "Value": "<DIMENSION_VALUE>"
      }
    ],
    "Statistic": "Average",
    "Unit": "Percent"
  },
...
}
JSON

Serverless v2 읽기 전용 인스턴스를 활용한 Aurora Hybrid Cluster 아키텍처를 사용하면 상대적으로 더 높은 트래픽도 안정적으로 서비스할 수 있게 됩니다. 케이타운포유는 이 장점을 살려 기존 프로비저닝 된 Aurora Cluster의 오토스케일링에 구성하였던 targetValue 보다 높게 구성할 수 있었습니다.

커스텀 Aurora 오토스케일링 아키텍처 검증하기

테스트 환경

  • Amazon Aurora Cluster : r5.4xlarge x 2 (1 write, 1 read) + Serverless x 1 (1 read/ACU 0.5~128)
  • Provisioned Instance Type: c5.2xlarge
  • Scale-out 임계치 : 40%
  • 프로비저닝 / 서러비스 트래픽 전환 임계치 : 40% / 60%
  • Serverless v2 읽기 전용 인스턴스 트래픽 전환 비율 : 20% (프로비저닝 된 인스턴스 기준)

테스트 시나리오

  1. 테스트 시작 직후 급격한 트래픽을 주어 데이터베이스에 부하 발생
  2. 임계치 60%를 초과하면서 서버리스로 트래픽 전환 시작
  3. 프로비저닝 / 서버리스 엔드포인트 간의 트래픽 비율 미세 조절
  4. Scale-out ~ traffic shifting high threshold 사이의 CPU 구간으로 수렴
  5. Scale-out 발생
  6. read replica 추가로 프로비저닝 된 인스턴스의 CPU 부하 감소
  7. 위 과정 반복
  8. 테스트 종료

테스트 결과

AS-IS : Aurora Cluster (Provisioned Only)

평균 CPU 사용률

기존 프로비저닝 된 Aurora 읽기 전용 인스턴스만으로 구성된 Cluster에서는 위 그림과 같이 테스트 시작 직후 발생한 다량의 트래픽으로 인해 10여분간 CPU 사용량 100% 도달 및 서비스 장애가 발생하였습니다.

TO-BE : Aurora Hybrid Cluster (Provisioned + Serverless v2)

평균 CPU 사용률

개선 전 Aurora Cluster에서 Scale-out이 일어나는 약 10여분간 100%의 CPU 사용량을 유지하는 것과 달리, Aurora Hybrid Cluster에서는 순간적인 CPU 부하 증가를 감지 후 Serverless v2 읽기 전용 인스턴스로 트래픽을 전환하여 안정적인 CPU 사용량을 유지하는 것을 확인할 수 있었습니다.

엔드포인트 별 커넥션 증가율

또한, 오토 스케일일링으로 인해 Scale-out이 일어난 후에는 Serverless v2 읽기 전용 인스턴스로 전환되는 트래픽의 비율을 점진적으로 낮추고 Scale-out이 완료된 이후에는 모든 트래픽이 Provisioned 인스턴스로 전환되는 것을 확인할 수 있습니다.

결과

기존 프로비저닝 된 Aurora Cluster와 Aurora Serverless v2 인스턴스를 동시 접목한 Aurora Hybrid Cluster 아키텍처로 인해 케이타운포유는 크게 다음과 같은 부분에서 시스템이 개선되었습니다.

서비스 안정성

Aurora Hybrid Cluster 아키텍처는 이벤트 전 예상치 못한 글로벌 스파이크 트래픽이 발생하여도 Serverless v2 인스턴스를 통해 안전하게 트래픽에 대한 대응이 가능합니다.

시스템 성능

데이터베이스의 안정적인 사용량은 어플리케이션의 성능과도 연관됩니다. 테스트 결과 초기 데이터베이스 과부하 상태에서 테스트 쿼리에 대한 응답속도는 하이브리드 구성에서 평균 80ms, 미구성일 경우 220ms 확연한 차이가 나타납니다.

오토스케일링 시간 단축

AWS namespace를 가지는 메트릭의 알람 데이터 주기는 최소 1분입니다. 하지만 별도의 커스텀 메트릭을 통하여 데이터 포인트 주기를 최대 10초로 낮출 수 있습니다. 이 고해상도 메트릭을 통해서 오토스케일링은 더욱 빠르게 반영됩니다.

비용 절감

케이타운포유는 이벤트 대응을 위해 최소 1시간 이전에 읽기 전용 인스턴스를 증설합니다. 서비스의 안정성을 최우선으로 하기때문에 읽기 전용 인스턴스는 예측되는 트래픽보다 최소 20% 이상 추가 증설합니다. 하지만 일부 이벤트는 정확한 시간을 미리 알 수 없기 때문에 수시간 전 미리 증설하기도 하며 이로 인하여 발생하는 비용 누수는 한달에 수십대*시간의 규모입니다. Aurora Hybrid Cluster 아키텍처는 사전 불필요한 인스턴스 증설이 필요없기 때문에 약 30%의 이벤트 비용을 절감할 수 있었습니다.

위 사항을 정리하면 다음과 같습니다.

  기존 클러스터 하이브리드 클러스터
서비스 장애 시간 평균 15분 내외 장애 없음
장애구간 서비스 응답 속도 평균 220ms 평균 80ms
서비스 비용 비용 추가 발생 (CloudWatch, Lambda 등) 비용 추가 발생 (CloudWatch, Lambda 등)
이벤트 대응 비용 (비율) 100% 70% (30% 절감)
오토스케일링 정밀도 최대 1분 최대 10초

이처럼 Aurora Hybrid Cluster 아키텍처 구성을 통해 예측하지 못한 순간적인 스파이트 트래픽에 대해서 신속한 트래픽 전환을 통해 서비스의 안정성을 빠르게 회복할 수 있었습니다. 그리고 시스템 성능, 빠른 대응이 가능한 오토스케일링, 그리고 비용 측면에서 크게 개선되었습니다.

마무리

기업의 64%가 1년 안에 서비스 중단을 경험했으며 평균 25시간 이상의 다운타임을 겪은 것으로 조사되었습니다. 또한 ITIC가 1,000명 이상의 직원을 보유한 기업을 대상으로 한 조사 결과에 따르면 해당 기업의 98%는 서비스 중단에 따른 손실이 시간당 1억원을 초과합니다. 서비스 중단을 최소화하여 손실을 막고 비즈니스의 성과를 달성하기 위한 노력은 중요합니다.

Aurora Serverless v2 인스턴스를 지원하는 엔진은 Aurora MySQL 3 이상, Aurora PostgreSQL 13, Aurora PostgreSQL 14, Aurora PostgreSQL 15 입니다. Aurora Cluster의 엔진 버전이 낮은 경우, Aurora Serverless v2를 사용하도록 기존 클러스터 업그레이드 또는 전환을 참조 하시거나, 새로운 Blue/Green Deployment 기능을 활용하여 DB 업그레이드 작업을 더 안전하고 빠르게 업그레이드 진행을 수행하시기를 권장 드립니다.

Aurora Hybrid(Mixed-Configuration) Cluster를 활용한 개선된 커스텀 오토스케일링 아키텍처는 AWS에서 제공하는 다양한 API 기반의 기능과 로깅을 활용하여 쉽고 빠르게 구축할 수 있었습니다. 또한, 여러 번의 아키텍처 개선 과정을 거쳐 최종적으로 AWS에서 제공하는 Cloudwatch Log를 활용하여 Cloudwatch Alarm 기반으로 동작하도록 자동화하였기 때문에 운영, 관리 부담도 크게 줄일 수 있었습니다. 특히, 이러한 아키텍처는 데이터베이스 부하 대응을 위한 용도 이외, Serverless v2 읽기 전용 인스턴스를 다양한 용도로 활용 가능한 장점이 있습니다.

케이타운포유의 Aurora Hybrid Cluster 구축 사례가 서비스 안정성 향상과 성능, 비용 등 다양한 데이터베이스 문제를 겪고 있는 분들께 훌륭한 대안이 되기를 기대합니다.

참고

 

'Cloud' 카테고리의 다른 글

Secure Connectivity from Public to Private: Introducing EC2 Instance Connect Endpoint  (0) 2023.06.20
컨테이너  (0) 2022.08.15
728x90

일단 서두적으로 말하자면 모야모 샵에 들어갈 때에 로그인이 해제되는 현상이 발생했다.

 

완전하게 알고있지는 못하지만, 대표님의 말에 의하면 예전에도 이런 현상이 있었고 이러한 부분은 예전에도 발생했었으나 해결됬었던 상태라고 해서 여러가지 경우의 수를 가정해 보았다.

 

 

1. Kakao SDK v1 이 2023년 04월 30일 정식종료되고 Maven 에서 호출하는 repository 에 대한 문제가 생긴 이유

사실 이 이유에 대한 부분으로는 크게 생각하는 부분은 아니였는데 가정상 충분히 가능할 수 있다는 생각이 들었다.

일부 사용자에게서 카카오톡을 사용한 로그인 시 로그인이 안된다는 보고가 있었으나, 내 경우 예전에 잡아둔 어플리케이션 설치 기반에서 호출이 거부당하진 않았기 때문이다.

 

실제적으로 조사해보면 Kakao SDK v1 에서 v2 로 마이그레이션 하는데에 대한 유예기간을 11월 말까지로 줫기 때문에 이 부분이 크게 문제될 것이라 생각할 수 있을까?

- 결과적으로 Kakao SDK v1 에 대한 end-point 호출 경로부터 시작해서 여러가지 수정사항을 거치지 않으면 Android Studio 에서는 에뮬레이터 조차 실행되지 않았다.

 

이러한 부분에서 Yagnesh 가 작업하고 해당 부분에 대해서 업데이트 배포를 한 이후에 신기하게도 issue 가 현저하게 줄어들었다.

 

1.1 여러가지 경우의 수를 생각해본다.

이 부분에서 Kakao SDK v1 에 대한 호출로 인한 문제가 되었을 것이라면, 분명히 데이터베이스 안에 access_token 이 정상적으로 저장되지 않거나, update_token 에 대한 update REST가 실패로 잡혀서 통신이 돌아갔을 것이다.

 

그러면 이 부분에서 더 조사해봐야 할 것은 무엇이냐? 로그 모니터링 결과값인데 실제적으로 application.log 쪽에서 살펴본 바로는 REST의 호출 이상은 크게 잡히지 않았다.

일단 error_log 쪽을 조금 더 파봐야 할 것 같다.

728x90

by Sheila Busser | on 14 JUN 2023 | in Amazon EC2, Amazon VPC, Announcements, Compute, Customer Solutions, Security, Identity, & Compliance, Technical How-To | Permalink |  Share

 

 

This blog post is written by Ariana Rahgozar, Solutions Architect, and Kenneth Kitts, Sr. Technical Account Manager, AWS.

Imagine trying to connect to an Amazon Elastic Compute Cloud (Amazon EC2) instance within your Amazon Virtual Private Cloud (Amazon VPC) over the Internet. Typically, you’d first have to connect to a bastion host with a public IP address that your administrator set up over an Internet Gateway (IGW) in your VPC, and then use port forwarding to reach your destination.

Today we launched Amazon EC2 Instance Connect (EIC) Endpoint, a new feature that allows you to connect securely to your instances and other VPC resources from the Internet. With EIC Endpoint, you no longer need an IGW in your VPC, a public IP address on your resource, a bastion host, or any agent to connect to your resources. EIC Endpoint combines identity-based and network-based access controls, providing the isolation, control, and logging needed to meet your organization’s security requirements. As a bonus, your organization administrator is also relieved of the operational overhead of maintaining and patching bastion hosts for connectivity. EIC Endpoint works with the AWS Management Console and AWS Command Line Interface (AWS CLI). Furthermore, it gives you the flexibility to continue using your favorite tools, such as PuTTY and OpenSSH.

In this post, we provide an overview of how the EIC Endpoint works and its security controls, guide you through your first EIC Endpoint creation, and demonstrate how to SSH to an instance from the Internet over the EIC Endpoint.

 


AWS EC2 를 이용하여 Private Network 와 Public Network 를 더 쉽게 설계할 수 있는 방법이 등장했다.

 

기존에는 LB - WEB - WAS - DB 정도로 이루어진 아키텍쳐가 어느정도 처리를 가지고 있었다고 가정하면, 이제는 쓸데없이 WEB쪽에서 Reverse Proxy 를 이용하여 전달할 필요가 없다는 이야기이다.

(물론 이  경우에도 분명히 말하지만, 당시 회사에서는 WEB 부분에서 실질적으로 처리되는 것이라곤 nginx 에서 설정하는 일부 값이였기에 굉장히 무의미하긴 했다.)

 

결과적으로 아마 내, 외부망 분리라고 하는 것은 VPC 에 있는 IGW에 노출시키고 그 발이 닿는것이냐에 대한 질문이 아니였을까 생각한다.


EIC Endpoint product overview

EIC Endpoint is an identity-aware TCP proxy. It has two modes: first, AWS CLI client is used to create a secure, WebSocket tunnel from your workstation to the endpoint with your AWS Identity and Access Management (IAM) credentials. Once you’ve established a tunnel, you point your preferred client at your loopback address (127.0.0.1 or localhost) and connect as usual. Second, when not using the AWS CLI, the Console gives you secure and seamless access to resources inside your VPC. Authentication and authorization is evaluated before traffic reaches the VPC. The following figure shows an illustration of a user connecting via an EIC Endpoint:

Figure 1. User connecting to private EC2 instances through an EIC Endpoint

EIC Endpoints provide a high degree of flexibility. First, they don’t require your VPC to have direct Internet connectivity using an IGW or NAT Gateway. Second, no agent is needed on the resource you wish to connect to, allowing for easy remote administration of resources which may not support agents, like third-party appliances. Third, they preserve existing workflows, enabling you to continue using your preferred client software on your local workstation to connect and manage your resources. And finally, IAM and Security Groups can be used to control access, which we discuss in more detail in the next section.

Prior to the launch of EIC Endpoints, AWS offered two key services to help manage access from public address space into a VPC more carefully. First is EC2 Instance Connect, which provides a mechanism that uses IAM credentials to push ephemeral SSH keys to an instance, making long-lived keys unnecessary. However, until now EC2 Instance Connect required a public IP address on your instance when connecting over the Internet. With this launch, you can use EC2 Instance Connect with EIC Endpoints, combining the two capabilities to give you ephemeral-key-based SSH to your instances without exposure to the public Internet. As an alternative to EC2 Instance Connect and EIC Endpoint based connectivity, AWS also offers Systems Manager Session Manager (SSM), which provides agent-based connectivity to instances. SSM uses IAM for authentication and authorization, and is ideal for environments where an agent can be configured to run.

Given that EIC Endpoint enables access to private resources from public IP space, let’s review the security controls and capabilities in more detail before discussing creating your first EIC Endpoint.

Security capabilities and controls

Many AWS customers remotely managing resources inside their VPCs from the Internet still use either public IP addresses on the relevant resources, or at best a bastion host approach combined with long-lived SSH keys. Using public IPs can be locked down somewhat using IGW routes and/or security groups. However, in a dynamic environment those controls can be hard to manage. As a result, careful management of long-lived SSH keys remains the only layer of defense, which isn’t great since we all know that these controls sometimes fail, and so defense-in-depth is important. Although bastion hosts can help, they increase the operational overhead of managing, patching, and maintaining infrastructure significantly.

IAM authorization is required to create the EIC Endpoint and also to establish a connection via the endpoint’s secure tunneling technology. Along with identity-based access controls governing who, how, when, and how long users can connect, more traditional network access controls like security groups can also be used. Security groups associated with your VPC resources can be used to grant/deny access. Whether it’s IAM policies or security groups, the default behavior is to deny traffic unless it is explicitly allowed.

EIC Endpoint meets important security requirements in terms of separation of privileges for the control plane and data plane. An administrator with full EC2 IAM privileges can create and control EIC Endpoints (the control plane). However, they cannot use those endpoints without also having EC2 Instance Connect IAM privileges (the data plane). Conversely, DevOps engineers who may need to use EIC Endpoint to tunnel into VPC resources do not require control-plane privileges to do so. In all cases, IAM principals using an EIC Endpoint must be part of the same AWS account (either directly or by cross-account role assumption). Security administrators and auditors have a centralized view of endpoint activity as all API calls for configuring and connecting via the EIC Endpoint API are recorded in AWS CloudTrail. Records of data-plane connections include the IAM principal making the request, their source IP address, the requested destination IP address, and the destination port. See the following figure for an example CloudTrail entry.

 Figure 2. Partial CloudTrail entry for an SSH data-plane connection

EIC Endpoint supports the optional use of Client IP Preservation (a.k.a Source IP Preservation), which is an important security consideration for certain organizations. For example, suppose the resource you are connecting to has network access controls that are scoped to your specific public IP address, or your instance access logs must contain the client’s “true” IP address. Although you may choose to enable this feature when you create an endpoint, the default setting is off. When off, connections proxied through the endpoint use the endpoint’s private IP address in the network packets’ source IP field. This default behavior allows connections proxied through the endpoint to reach as far as your route tables permit. Remember, no matter how you configure this setting, CloudTrail records the client’s true IP address.

EIC Endpoints strengthen security by combining identity-based authentication and authorization with traditional network-perimeter controls and provides for fine-grained access control, logging, monitoring, and more defense in depth. Moreover, it does all this without requiring Internet-enabling infrastructure in your VPC, minimizing the possibility of unintended access to private VPC resources.

Getting started

Creating your EIC Endpoint

Only one endpoint is required per VPC. To create or modify an endpoint and connect to a resource, a user must have the required IAM permissions, and any security groups associated with your VPC resources must have a rule to allow connectivity. Refer to the following resources for more details on configuring security groups and sample IAM permissions.

The AWS CLI or Console can be used to create an EIC Endpoint, and we demonstrate the AWS CLI in the following. To create an EIC Endpoint using the Console, refer to the documentation.

Creating an EIC Endpoint with the AWS CLI

To create an EIC Endpoint with the AWS CLI, run the following command, replacing [SUBNET] with your subnet ID and [SG-ID] with your security group ID:

aws ec2 create-instance-connect-endpoint \
    --subnet-id [SUBNET] \
    --security-group-id [SG-ID]

After creating an EIC Endpoint using the AWS CLI or Console, and granting the user IAM permission to create a tunnel, a connection can be established. Now we discuss how to connect to Linux instances using SSH. However, note that you can also use the OpenTunnel API to connect to instances via RDP.

Connecting to your Linux Instance using SSH

With your EIC Endpoint set up in your VPC subnet, you can connect using SSH. Traditionally, access to an EC2 instance using SSH was controlled by key pairs and network access controls. With EIC Endpoint, an additional layer of control is enabled through IAM policy, leading to an enhanced security posture for remote access. We describe two methods to connect via SSH in the following.

One-click command

To further reduce the operational burden of creating and rotating SSH keys, you can use the new ec2-instance-connect ssh command from the AWS CLI. With this new command, we generate ephemeral keys for you to connect to your instance. Note that this command requires use of the OpenSSH client. To use this command and connect, you need IAM permissions as detailed here.

Once configured, you can connect using the new AWS CLI command, shown in the following figure:

Figure 3. AWS CLI view upon successful SSH connection to your instance

To test connecting to your instance from the AWS CLI, you can run the following command where [INSTANCE] is the instance ID of your EC2 instance:

aws ec2-instance-connect ssh --instance-id [INSTANCE]

Note that you can still use long-lived SSH credentials to connect if you must maintain existing workflows, which we will show in the following. However, note that dynamic, frequently rotated credentials are generally safer.

Open-tunnel command

You can also connect using SSH with standard tooling or using the proxy command. To establish a private tunnel (TCP proxy) to the instance, you must run one AWS CLI command, which you can see in the following figure:

Figure 4. AWS CLI view after running new SSH open-tunnel command, creating a private tunnel to connect to our EC2 instance

You can run the following command to test connectivity, where [INSTANCE] is the instance ID of your EC2 instance and [SSH-KEY] is the location and name of your SSH key. For guidance on the use of SSH keys, refer to our documentation on Amazon EC2 key pairs and Linux instances.

ssh ec2-user@[INSTANCE] \
    -i [SSH-KEY] \
    -o ProxyCommand='aws ec2-instance-connect open-tunnel \
    --instance-id %h'

Once we have our EIC Endpoint configured, we can SSH into our EC2 instances without a public IP or IGW using the AWS CLI.

Conclusion

EIC Endpoint provides a secure solution to connect to your instances via SSH or RDP in private subnets without IGWs, public IPs, agents, and bastion hosts. By configuring an EIC Endpoint for your VPC, you can securely connect using your existing client tools or the Console/AWS CLI. To learn more, visit the EIC Endpoint documentation.

+ Recent posts