๋ฉด์ ๊ด: "๋๊ท๋ชจ ์กฐ์ง์์ ์ฌ์ฉํ ์ ์๋ CI/CD ํ์ดํ๋ผ์ธ ์์คํ ์ ์ค๊ณํด์ฃผ์ธ์. ๋ค์ค ๋ ํฌ์งํ ๋ฆฌ ์ง์, ๋ณ๋ ฌ ๋น๋, ํ ์คํธ ์๋ํ, ๋ฐฐํฌ ์ ๋ต์ด ํ์ํฉ๋๋ค."
์ง์์: ๋ค, ๋ช ๊ฐ์ง ์๊ตฌ์ฌํญ์ ํ์ธํ๊ณ ์ถ์ต๋๋ค.
- ์ผ์ผ ๋น๋ ์์ ๋์ ๋น๋ ์๋ ์ด๋ ์ ๋์ธ๊ฐ์?
- ๋น๋/ํ ์คํธ ํ๊ฒฝ์ ๊ฒฉ๋ฆฌ ์์ค์ ์ด๋ป๊ฒ ๋๋์?
- ์ํฐํฉํธ ๋ณด๊ด ๊ธฐ๊ฐ๊ณผ ํฌ๊ธฐ ์ ํ์ด ์๋์?
- ๋น๋ ์คํจ ์ ์๋ฆผ์ด๋ ๋กค๋ฐฑ ์ ๋ต์ด ํ์ํ๊ฐ์?
๋ฉด์ ๊ด:
- ์ผ์ผ 1000ํ ๋น๋, ์ต๋ 100๊ฐ ๋์ ๋น๋
- ์์ ํ ์ปจํ ์ด๋ ๊ฒฉ๋ฆฌ ํ์, ๋ฏผ๊ฐํ ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ ํฌํจ
- ์ํฐํฉํธ๋ 90์ผ ๋ณด๊ด, ์ ์ฅ์๋น ์ต๋ 500GB
- Slack/์ด๋ฉ์ผ ์๋ฆผ, ์๋ ๋กค๋ฐฑ ๊ธฐ๋ฅ ํ์
@Service
public class PipelineService {
private final JobExecutor jobExecutor;
private final ArtifactStorage artifactStorage;
private final NotificationService notificationService;
// 1. ํ์ดํ๋ผ์ธ ์ ์
public class Pipeline {
private final String pipelineId;
private final List<Stage> stages;
private final Map<String, String> environment;
@Data
public class Stage {
private final String stageName;
private final List<Job> jobs;
private final StageType type; // BUILD, TEST, DEPLOY
private final RetryPolicy retryPolicy;
private final List<String> dependencies;
}
public PipelineExecution execute() {
// DAG ๊ธฐ๋ฐ ์คํ
์ด์ง ์คํ
return stages.stream()
.filter(stage -> canExecuteStage(stage))
.map(stage -> executeStage(stage))
.collect(Collectors.toList());
}
}
// 2. ์์
์คํ ์์ง
@Service
public class JobExecutor {
private final KubernetesClient kubernetesClient;
private final SecretManager secretManager;
public JobResult executeJob(Job job) {
// ์์
ํ๊ฒฝ ์ค๋น
Pod pod = preparePodSpec(job);
// ํ๊ฒฝ ๋ณ์ ๋ฐ ์ํฌ๋ฆฟ ์ฃผ์
injectSecrets(pod, job.getSecrets());
// ์์
์คํ
try {
kubernetesClient.pods().create(pod);
return watchJobCompletion(pod.getMetadata().getName());
} catch (Exception e) {
handleJobFailure(job, e);
return JobResult.failure(e);
}
}
private Pod preparePodSpec(Job job) {
return new PodBuilder()
.withNewMetadata()
.withName(job.getId())
.withLabels(job.getLabels())
.endMetadata()
.withNewSpec()
.withContainers(createJobContainer(job))
.withRestartPolicy("Never")
.withServiceAccount("ci-runner")
.endSpec()
.build();
}
}
// 3. ์ํฐํฉํธ ๊ด๋ฆฌ
@Service
public class ArtifactManager {
private final S3Client s3Client;
private final ArtifactMetadataRepository metadataRepo;
public void storeArtifact(String pipelineId,
MultipartFile artifact) {
String artifactPath = generateArtifactPath(pipelineId);
// S3์ ์ํฐํฉํธ ์ ์ฅ
s3Client.putObject(PutObjectRequest.builder()
.bucket(ARTIFACT_BUCKET)
.key(artifactPath)
.build(),
RequestBody.fromInputStream(
artifact.getInputStream(),
artifact.getSize()
));
// ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
ArtifactMetadata metadata = ArtifactMetadata.builder()
.pipelineId(pipelineId)
.path(artifactPath)
.size(artifact.getSize())
.createdAt(Instant.now())
.build();
metadataRepo.save(metadata);
}
// ์ํฐํฉํธ ๋ณด๊ด ์ ์ฑ
์ ์ฉ
@Scheduled(cron = "0 0 0 * * *") // ๋งค์ผ ์์
public void applyRetentionPolicy() {
LocalDate cutoffDate =
LocalDate.now().minusDays(90);
List<ArtifactMetadata> expiredArtifacts =
metadataRepo.findByCreatedAtBefore(cutoffDate);
expiredArtifacts.forEach(artifact -> {
s3Client.deleteObject(DeleteObjectRequest.builder()
.bucket(ARTIFACT_BUCKET)
.key(artifact.getPath())
.build());
metadataRepo.delete(artifact);
});
}
}
}@Service
public class PipelineExecutionService {
// 1. ๋ณ๋ ฌ ์คํ ๊ด๋ฆฌ
public class ParallelExecutionManager {
private final ExecutorService executorService;
private final SemaphoreManager semaphoreManager;
public ParallelExecutionManager() {
this.executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2
);
this.semaphoreManager = new SemaphoreManager(100); // ์ต๋ 100๊ฐ ๋์ ์คํ
}
public CompletableFuture<StageResult> executeStageInParallel(Stage stage) {
return CompletableFuture.supplyAsync(() -> {
String resourceType = stage.getResourceRequirements().getType();
try {
semaphoreManager.acquire(resourceType);
return executeStage(stage);
} finally {
semaphoreManager.release(resourceType);
}
}, executorService);
}
}
// 2. ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
@Service
public class PipelineMonitor {
private final MetricsRegistry metricsRegistry;
private final AlertService alertService;
public void monitorPipeline(String pipelineId) {
Pipeline pipeline = getPipeline(pipelineId);
// ๋น๋ ๋งคํธ๋ฆญ์ค ์์ง
metricsRegistry.gauge(
"pipeline.duration",
pipeline.getDuration().toSeconds()
);
metricsRegistry.counter(
"pipeline.stages.total",
pipeline.getStages().size()
);
// ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
if (pipeline.getDuration().toMinutes() > 30) {
alertService.sendAlert(
AlertLevel.WARNING,
"Pipeline duration exceeds 30 minutes: " + pipelineId
);
}
// ๋ฆฌ์์ค ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
monitorResourceUsage(pipeline);
}
private void monitorResourceUsage(Pipeline pipeline) {
ResourceUsage usage = pipeline.getResourceUsage();
if (usage.getCpuUsage() > 80) {
alertService.sendAlert(
AlertLevel.CRITICAL,
"High CPU usage in pipeline: " + pipeline.getId()
);
}
}
}
// 3. ๋ก๊ทธ ์ง๊ณ ์์คํ
@Service
public class LogAggregator {
private final ElasticsearchClient esClient;
private final KafkaTemplate<String, LogEvent> kafkaTemplate;
@KafkaListener(topics = "pipeline-logs")
public void handleLogs(LogEvent logEvent) {
// ๋ก๊ทธ ์ธ๋ฑ์ฑ
IndexRequest<LogEvent> request = IndexRequest.of(r -> r
.index("pipeline-logs-" +
LocalDate.now().format(DateTimeFormatter.ISO_DATE))
.document(logEvent)
);
esClient.index(request);
// ์ค์๊ฐ ๋ก๊ทธ ์คํธ๋ฆฌ๋ฐ
if (logEvent.getLevel().equals(LogLevel.ERROR)) {
notifyError(logEvent);
}
}
public List<LogEvent> queryLogs(String pipelineId,
TimeRange range) {
// ๋ก๊ทธ ๊ฒ์ ์ฟผ๋ฆฌ
SearchRequest request = SearchRequest.of(r -> r
.index("pipeline-logs-*")
.query(q -> q
.bool(b -> b
.must(m -> m
.term(t -> t
.field("pipelineId")
.value(pipelineId)))
.must(m -> m
.range(ra -> ra
.field("timestamp")
.gte(JsonData.of(range.getStart()))
.lte(JsonData.of(range.getEnd()))))))
);
return esClient.search(request, LogEvent.class)
.hits()
.hits()
.stream()
.map(hit -> hit.source())
.collect(Collectors.toList());
}
}
// 4. ์คํจ ๋ถ์ ๋ฐ ์๋ ๋ณต๊ตฌ
@Service
public class FailureAnalyzer {
public void analyzePipelineFailure(Pipeline pipeline) {
FailureReport report = FailureReport.builder()
.pipelineId(pipeline.getId())
.failureStage(pipeline.getFailedStage())
.logs(extractRelevantLogs(pipeline))
.resourceUsage(pipeline.getResourceUsage())
.build();
// ์คํจ ํจํด ๋ถ์
FailurePattern pattern = detectFailurePattern(report);
// ์๋ ๋ณต๊ตฌ ์๋
if (pattern.isAutoRecoverable()) {
attemptRecovery(pipeline, pattern);
} else {
// ์๋ ๊ฐ์
ํ์
notifyTeam(report);
}
}
private void attemptRecovery(Pipeline pipeline,
FailurePattern pattern) {
switch (pattern.getType()) {
case RESOURCE_EXHAUSTION:
scaleResources(pipeline);
break;
case TRANSIENT_FAILURE:
retryPipeline(pipeline);
break;
case DEPENDENCY_FAILURE:
updateDependencies(pipeline);
break;
}
}
}
}์ด๋ฌํ ๋ชจ๋ํฐ๋ง ๋ฐ ์คํ ์์คํ ์ ํตํด:
-
๋ณ๋ ฌ ์คํ ๊ด๋ฆฌ
- ๋ฆฌ์์ค ๊ธฐ๋ฐ ์ ํ
- ์ธ๋งํฌ์ด๋ฅผ ํตํ ๋์์ฑ ์ ์ด
- ํจ์จ์ ์ธ ์ค์ผ์ค๋ง
-
์ค์๊ฐ ๋ชจ๋ํฐ๋ง
- ์ฑ๋ฅ ๋ฉํธ๋ฆญ ์์ง
- ๋ฆฌ์์ค ์ฌ์ฉ๋ ์ถ์
- ์๋ฆผ ์์คํ
-
๋ก๊ทธ ๊ด๋ฆฌ
- ์ค์๊ฐ ๋ก๊ทธ ์์ง
- ๊ฒ์ ๊ฐ๋ฅํ ๋ก๊ทธ ์ ์ฅ
- ์๋ฌ ๊ฐ์ง ๋ฐ ์๋ฆผ
-
์คํจ ๋ถ์
- ํจํด ๊ธฐ๋ฐ ๋ถ์
- ์๋ ๋ณต๊ตฌ ์๋
- ํ ์๋ฆผ
์ ๊ตฌํํ ์ ์์ต๋๋ค.
๋ฉด์ ๊ด: ๋๊ท๋ชจ ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์์ ์ฌ๋ฌ ์๋น์ค์ CI/CD๋ฅผ ์ด๋ป๊ฒ ์กฐ์ ํ์๊ฒ ์ต๋๊น?
@Service
public class MicroserviceCICoordinator {
// 1. ์์กด์ฑ ๊ทธ๋ํ ๊ด๋ฆฌ
public class DependencyGraphManager {
private final Map<String, ServiceNode> serviceGraph = new ConcurrentHashMap<>();
@Data
public class ServiceNode {
private final String serviceId;
private final Set<String> dependencies;
private final Set<String> dependents;
private ServiceVersion currentVersion;
private DeploymentStatus status;
}
// ์์กด์ฑ ๊ธฐ๋ฐ ๋น๋ ์์ ๊ฒฐ์
public List<String> determineBuildOrder() {
// ์์ ์ ๋ ฌ ๊ตฌํ
Set<String> visited = new HashSet<>();
List<String> buildOrder = new ArrayList<>();
serviceGraph.keySet().forEach(serviceId -> {
if (!visited.contains(serviceId)) {
topologicalSort(serviceId, visited, buildOrder);
}
});
return buildOrder;
}
// ์ํฅ๋ ๋ถ์
public Set<String> analyzeImpact(String changedService) {
Set<String> impactedServices = new HashSet<>();
Queue<String> queue = new LinkedList<>();
queue.add(changedService);
while (!queue.isEmpty()) {
String service = queue.poll();
ServiceNode node = serviceGraph.get(service);
node.getDependents().forEach(dependent -> {
if (impactedServices.add(dependent)) {
queue.add(dependent);
}
});
}
return impactedServices;
}
}
// 2. ๋ฒ์ ๊ด๋ฆฌ ๋ฐ ํธํ์ฑ ์ฒดํฌ
@Service
public class VersionCompatibilityManager {
private final ContractTestRunner contractTestRunner;
public boolean checkCompatibility(ServiceVersion newVersion,
Set<ServiceVersion> dependencyVersions) {
// API ๊ณ์ฝ ํ
์คํธ ์คํ
ContractTestResult result = contractTestRunner
.runTests(newVersion, dependencyVersions);
if (!result.isCompatible()) {
handleCompatibilityFailure(result);
return false;
}
// ์ฑ๋ฅ ํ๊ท ํ
์คํธ
PerformanceTestResult perfResult =
runPerformanceTests(newVersion);
return perfResult.meetsThreshold();
}
private void handleCompatibilityFailure(ContractTestResult result) {
// ์คํจํ ๊ณ์ฝ ํ
์คํธ ๋ถ์
List<FailedContract> failedContracts = result.getFailedContracts();
// ์ํฅ๋ฐ๋ ์๋น์ค ํ์ ์๋ฆผ
notifyTeams(failedContracts);
// ๋ณ๊ฒฝ ๊ฐ์ด๋ ์์ฑ
generateChangeGuide(failedContracts);
}
}
// 3. ํตํฉ ํ
์คํธ ์ค์ผ์คํธ๋ ์ด์
@Service
public class IntegrationTestOrchestrator {
private final KubernetesClient kubernetesClient;
private final TestEnvironmentManager testEnvManager;
public IntegrationTestResult runIntegrationTests(
Set<MicroserviceDeployment> deployments) {
// ํ
์คํธ ํ๊ฒฝ ํ๋ก๋น์ ๋
TestEnvironment env = testEnvManager
.provisionEnvironment(deployments);
try {
// ์๋น์ค ๋ฐฐํฌ
deployServices(env, deployments);
// ํตํฉ ํ
์คํธ ์คํ
TestSuite integrationTests =
TestSuiteBuilder.forDeployments(deployments)
.withEnvironment(env)
.build();
return testRunner.runTests(integrationTests);
} finally {
// ํ๊ฒฝ ์ ๋ฆฌ
testEnvManager.cleanupEnvironment(env);
}
}
private void deployServices(TestEnvironment env,
Set<MicroserviceDeployment> deployments) {
// ๋ณ๋ ฌ ๋ฐฐํฌ ์คํ
CompletableFuture<?>[] deployFutures = deployments.stream()
.map(deployment -> CompletableFuture.runAsync(() ->
deployService(env, deployment)))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(deployFutures).join();
}
}
// 4. ๋กค์์ ์กฐ์
@Service
public class RolloutCoordinator {
private final DeploymentManager deploymentManager;
private final HealthCheckService healthCheckService;
public void coordinateRollout(
Set<MicroserviceDeployment> deployments) {
// ๋ฐฐํฌ ๊ทธ๋ฃน ์์ฑ
List<DeploymentGroup> deploymentGroups =
createDeploymentGroups(deployments);
for (DeploymentGroup group : deploymentGroups) {
// ๊ทธ๋ฃน๋ณ ๋กค์์
RolloutResult result = rolloutGroup(group);
if (!result.isSuccessful()) {
// ๋กค๋ฐฑ ์ฒ๋ฆฌ
handleRolloutFailure(group, result);
break;
}
// ์์ ํ ๋๊ธฐ
waitForStabilization(group);
}
}
private void waitForStabilization(DeploymentGroup group) {
// ํฌ์ค์ฒดํฌ ๋ฐ ๋ฉํธ๋ฆญ ๋ชจ๋ํฐ๋ง
Duration stabilizationPeriod = Duration.ofMinutes(15);
Instant deadline = Instant.now().plus(stabilizationPeriod);
while (Instant.now().isBefore(deadline)) {
if (!isGroupHealthy(group)) {
throw new StabilizationException("Group failed to stabilize");
}
Thread.sleep(Duration.ofSeconds(30).toMillis());
}
}
private boolean isGroupHealthy(DeploymentGroup group) {
return group.getDeployments().stream()
.allMatch(deployment ->
healthCheckService.isHealthy(deployment) &&
metricsService.isWithinThresholds(deployment));
}
}
}์ด๋ฌํ ๋ง์ดํฌ๋ก์๋น์ค CI/CD ์กฐ์ ์์คํ ์ ํตํด:
-
์์กด์ฑ ๊ด๋ฆฌ
- ์๋น์ค ๊ฐ ์์กด์ฑ ์ถ์
- ๋น๋ ์์ ์ต์ ํ
- ์ํฅ๋ ๋ถ์
-
๋ฒ์ ํธํ์ฑ
- API ๊ณ์ฝ ํ ์คํธ
- ์ฑ๋ฅ ํ๊ท ํ ์คํธ
- ํธํ์ฑ ๋ฌธ์ ์กฐ๊ธฐ ๋ฐ๊ฒฌ
-
ํตํฉ ํ ์คํธ
- ์๋ํ๋ ํ ์คํธ ํ๊ฒฝ
- ๋ณ๋ ฌ ํ ์คํธ ์คํ
- ํ๊ฒฝ ๊ด๋ฆฌ ์๋ํ
-
์กฐ์ ๋ ๋กค์์
- ๊ทธ๋ฃน ๊ธฐ๋ฐ ๋ฐฐํฌ
- ํฌ์ค์ฒดํฌ ๋ชจ๋ํฐ๋ง
- ์๋ ๋กค๋ฐฑ
์ ๊ตฌํํ์ฌ ๋๊ท๋ชจ ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์์์ CI/CD๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
ํนํ ์ค์ํ ์ ์:
- ์๋น์ค ๊ฐ ์์กด์ฑ ๊ด๋ฆฌ
- ์๋ํ๋ ํธํ์ฑ ๊ฒ์ฆ
- ์์ ์ ์ธ ๋กค์์ ์ ๋ต
- ํจ์จ์ ์ธ ํ ์คํธ ์คํ
์ ๋๋ค.