EnvironmentServiceImpl.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. package cn.seecoder.paas.service.impl;
  2. import cn.seecoder.paas.data.dao.ApplicationDAO;
  3. import cn.seecoder.paas.data.dao.ConfigDAO;
  4. import cn.seecoder.paas.data.dao.EnvironmentDAO;
  5. import cn.seecoder.paas.data.entity.Application;
  6. import cn.seecoder.paas.data.entity.Config;
  7. import cn.seecoder.paas.data.entity.Environment;
  8. import cn.seecoder.paas.service.EnvironmentService;
  9. import cn.seecoder.paas.service.facade.docker.DockerApi;
  10. import cn.seecoder.paas.service.facade.git.GitApi;
  11. import cn.seecoder.paas.service.facade.k8s.*;
  12. import cn.seecoder.paas.service.facade.k8s.model.*;
  13. import cn.seecoder.paas.service.facade.k8s.vo.SecretVO;
  14. import cn.seecoder.paas.service.model.converter.ConfigConverter;
  15. import cn.seecoder.paas.service.model.converter.EnvironmentConverter;
  16. import cn.seecoder.paas.service.model.vo.ConfigVO;
  17. import cn.seecoder.paas.service.model.vo.EnvironmentVO;
  18. import cn.seecoder.paas.util.ApplicationProperties;
  19. import cn.seecoder.paas.util.AsyncWrapper;
  20. import cn.seecoder.paas.util.LoggerUtil;
  21. import cn.seecoder.paas.util.ServiceException;
  22. import cn.seecoder.paas.util.enums.*;
  23. import com.spotify.docker.client.ProgressHandler;
  24. import com.spotify.docker.client.exceptions.DockerException;
  25. import com.spotify.docker.client.messages.ProgressMessage;
  26. import io.kubernetes.client.openapi.models.*;
  27. import org.apache.commons.lang.StringUtils;
  28. import org.eclipse.jgit.api.Git;
  29. import org.eclipse.jgit.api.ResetCommand;
  30. import org.slf4j.Logger;
  31. import org.springframework.beans.BeanUtils;
  32. import org.springframework.beans.factory.annotation.Autowired;
  33. import org.springframework.transaction.annotation.Transactional;
  34. import org.springframework.transaction.support.TransactionSynchronizationAdapter;
  35. import org.springframework.transaction.support.TransactionSynchronizationManager;
  36. import org.springframework.util.CollectionUtils;
  37. import java.nio.file.Files;
  38. import java.nio.file.Path;
  39. import java.nio.file.Paths;
  40. import java.time.LocalDateTime;
  41. import java.util.*;
  42. import java.util.stream.Collectors;
  43. @org.springframework.stereotype.Service
  44. public class EnvironmentServiceImpl implements EnvironmentService {
  45. private static final Logger logger = LoggerUtil.getLogger(EnvironmentServiceImpl.class);
  46. private final EnvironmentDAO environmentDAO;
  47. private final ApplicationDAO applicationDAO;
  48. private final ConfigDAO configDAO;
  49. private final PodApi podApi;
  50. private final DeploymentApi deploymentApi;
  51. private final ServiceApi serviceApi;
  52. private final IngressApi ingressApi;
  53. private final SecretApi secretApi;
  54. private final ConfigMapApi configMapApi;
  55. private final AsyncWrapper asyncWrapper;
  56. private final GitApi gitApi;
  57. private final DockerApi dockerApi;
  58. private final LogApi logApi;
  59. private final ApplicationProperties applicationProperties;
  60. private static final String DEFAULT_IMAGE_PULL_SECRET_NAME = "seecoder-paas-image-pull-secret";
  61. @Autowired
  62. public EnvironmentServiceImpl(EnvironmentDAO environmentDAO,
  63. ApplicationDAO applicationDAO,
  64. ConfigDAO configDAO,
  65. PodApi podApi,
  66. DeploymentApi deploymentApi,
  67. ServiceApi serviceApi,
  68. IngressApi ingressApi,
  69. SecretApi secretApi,
  70. ConfigMapApi configMapApi,
  71. GitApi gitApi,
  72. DockerApi dockerApi,
  73. LogApi logApi,
  74. ApplicationProperties properties,
  75. AsyncWrapper asyncWrapper) {
  76. this.environmentDAO = environmentDAO;
  77. this.applicationDAO = applicationDAO;
  78. this.configDAO = configDAO;
  79. this.podApi = podApi;
  80. this.deploymentApi = deploymentApi;
  81. this.serviceApi = serviceApi;
  82. this.ingressApi = ingressApi;
  83. this.secretApi = secretApi;
  84. this.configMapApi = configMapApi;
  85. this.gitApi = gitApi;
  86. this.dockerApi = dockerApi;
  87. this.logApi = logApi;
  88. this.applicationProperties = properties;
  89. this.asyncWrapper = asyncWrapper;
  90. }
  91. @Override
  92. @Transactional
  93. public EnvironmentVO createOrUpdate(EnvironmentVO environmentVO) throws ServiceException {
  94. Application application = applicationDAO.findById(environmentVO.getAppId()).orElse(null);
  95. if (application == null) {
  96. throw ServiceException.BAD_REQUEST;
  97. }
  98. Environment environment = EnvironmentConverter.convertToEntity(environmentVO);
  99. if (environmentVO.getId() != null) {
  100. environment = environmentDAO.findById(environmentVO.getId()).orElse(null);
  101. if (environment == null) {
  102. throw ServiceException.BAD_REQUEST;
  103. }
  104. BeanUtils.copyProperties(environmentVO, environment, "id", "appId", "buildStatus", "buildOutput", "deployStatus", "image", "buildStartTime", "deployStartTime", "deployOutput");
  105. }
  106. EnvironmentVO result = EnvironmentConverter.convertToVO(environmentDAO.save(environment));
  107. ConfigVO resultConfigVO;
  108. if (environmentVO.getId() == null) {
  109. // 是create
  110. // 获取
  111. Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.APPLICATION, application.getId());
  112. if (config == null) {
  113. config = ConfigConverter.convertToEntity(ConfigVO.getEmptyConfigVO(ConfigBelongsToType.ENVIRONMENT, result.getId()));
  114. }
  115. Config saved = new Config();
  116. BeanUtils.copyProperties(config, saved);
  117. saved.setId(null);
  118. saved.setConfigBelongs(ConfigBelongsToType.ENVIRONMENT);
  119. saved.setEntityId(result.getId());
  120. resultConfigVO = ConfigConverter.convertToVO(configDAO.save(saved));
  121. } else {
  122. resultConfigVO = ConfigConverter.convertToVO(configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, result.getId()));
  123. }
  124. result.setConfig(resultConfigVO);
  125. String labelKey = ResourceLabel.RESOURCE.getCode();
  126. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
  127. K8sObjectRequest request = K8sObjectRequest.builder().namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
  128. List<Pod> pods = podApi.getByCondition(request);
  129. result.setInstance(pods);
  130. return result;
  131. }
  132. @Override
  133. @Transactional
  134. public void delete(Integer id) throws ServiceException {
  135. Environment environment = environmentDAO.findById(id).orElse(null);
  136. if (environment == null) {
  137. throw ServiceException.BAD_REQUEST;
  138. }
  139. environmentDAO.deleteById(id);
  140. configDAO.deleteByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
  141. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(environment.getAppId()), String.valueOf(environment.getId()));
  142. String namespace = applicationProperties.getDeploymentNamespace();
  143. Map<String, String> labels = Collections.singletonMap(ResourceLabel.RESOURCE.getCode(), labelValue);
  144. AbstractApi[] deleteApis = {ingressApi, serviceApi, deploymentApi, configMapApi};
  145. for (AbstractApi api : deleteApis) {
  146. this.findAndDelete(api, namespace, labels);
  147. }
  148. }
  149. private void findAndDelete(AbstractApi abstractApi, String deployNamespace, Map<String, String> labels) {
  150. List<K8sAbstractObject> objects = abstractApi.getByCondition(K8sObjectRequest.builder().namespace(deployNamespace).labels(labels).build());
  151. if (!CollectionUtils.isEmpty(objects)) {
  152. for (K8sAbstractObject object : objects) {
  153. try {
  154. abstractApi.delete(object);
  155. } catch (Exception e) {
  156. LoggerUtil.error(logger, e, "deleteObjectError: object={}, labels={}", object, labels);
  157. }
  158. }
  159. }
  160. }
  161. @Override
  162. public EnvironmentVO get(Integer id, boolean fetchAll) throws ServiceException {
  163. Environment result = environmentDAO.findById(id).orElse(null);
  164. if (result == null) {
  165. throw ServiceException.BAD_REQUEST;
  166. }
  167. ConfigVO resultConfigVO = ConfigConverter.convertToVO(configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, result.getId()));
  168. EnvironmentVO vo = EnvironmentConverter.convertToVO(result);
  169. if (fetchAll) {
  170. vo.setBuildOutput(result.getBuildOutput());
  171. vo.setDeployOutput(result.getDeployOutput());
  172. }
  173. vo.setConfig(resultConfigVO);
  174. String labelKey = ResourceLabel.RESOURCE.getCode();
  175. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
  176. K8sObjectRequest request = K8sObjectRequest.builder().namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
  177. List<Pod> pods = podApi.getByCondition(request);
  178. vo.setInstance(pods);
  179. return vo;
  180. }
  181. @Override
  182. public List<EnvironmentVO> getListByAppId(Integer appId) {
  183. return environmentDAO.findAllByAppId(appId).stream().map(EnvironmentConverter::convertToVO).collect(Collectors.toList());
  184. }
  185. @Override
  186. @Transactional
  187. public EnvironmentVO deploy(Integer id) throws ServiceException {
  188. Environment result = environmentDAO.findById(id).orElse(null);
  189. if (result == null) {
  190. throw ServiceException.BAD_REQUEST;
  191. }
  192. if (result.getBuildStatus() == BuildStatus.BUILDING || result.getDeployStatus() == DeployStatus.DEPLOYING || result.getDeployStatus() == DeployStatus.RESTARTING) {
  193. throw new ServiceException("101", "存在正在进行的构建或部署!");
  194. }
  195. Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
  196. if (config == null) {
  197. throw ServiceException.INVALID_DATA;
  198. }
  199. result.setBuildOutput("");
  200. result.setBuildStatus(BuildStatus.BUILDING);
  201. result.setBuildStartTime(LocalDateTime.now());
  202. result.setDeployStatus(DeployStatus.NOT_STARTED);
  203. TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
  204. @Override
  205. public void afterCommit() {
  206. super.afterCommit();
  207. asyncWrapper.asyncInvoke(() -> buildAsync(id));
  208. }
  209. });
  210. return EnvironmentConverter.convertToVO(environmentDAO.save(result));
  211. }
  212. @Override
  213. public Map<String,String> getLog(Integer id, String podName) throws ServiceException {
  214. Environment result = environmentDAO.findById(id).orElse(null);
  215. if (result == null) {
  216. throw ServiceException.BAD_REQUEST;
  217. }
  218. String labelKey = ResourceLabel.RESOURCE.getCode();
  219. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
  220. K8sObjectRequest request = K8sObjectRequest.builder().name(podName).namespace(applicationProperties.getDeploymentNamespace()).labels(Collections.singletonMap(labelKey, labelValue)).build();
  221. List<Pod> pods = podApi.getByCondition(request);
  222. if (CollectionUtils.isEmpty(pods)) {
  223. return Collections.emptyMap();
  224. }
  225. Map<String, String> res = new HashMap<>();
  226. for (Container container : pods.get(0).getContainers()) {
  227. res.put(container.getName(), logApi.getAll(applicationProperties.getDeploymentNamespace(), pods.get(0).getName(), container.getName()));
  228. }
  229. return res;
  230. }
  231. @Override
  232. @Transactional
  233. public EnvironmentVO restart(Integer id, String podName) throws ServiceException {
  234. Environment result = environmentDAO.findById(id).orElse(null);
  235. if (result == null) {
  236. throw ServiceException.BAD_REQUEST;
  237. }
  238. if (result.getBuildStatus() == BuildStatus.BUILDING || result.getDeployStatus() == DeployStatus.DEPLOYING || result.getDeployStatus() == DeployStatus.RESTARTING) {
  239. throw new ServiceException("101", "存在正在进行的构建或部署!");
  240. }
  241. Pod pod = new Pod();
  242. pod.setName(podName);
  243. pod.setNamespace(applicationProperties.getDeploymentNamespace());
  244. podApi.delete(pod);
  245. result.setDeployStatus(DeployStatus.RESTARTING);
  246. result.setDeployStartTime(LocalDateTime.now());
  247. return EnvironmentConverter.convertToVO(environmentDAO.save(result));
  248. }
  249. @Override
  250. @Transactional
  251. public void checkDeployStatus() {
  252. LocalDateTime startTime = LocalDateTime.now().minusMinutes(10);
  253. List<Environment> environments = environmentDAO.findAllByBuildStatusAndBuildStartTimeBefore(BuildStatus.BUILDING, startTime);
  254. if (!CollectionUtils.isEmpty(environments)) {
  255. for (Environment environment : environments) {
  256. environment.setBuildStatus(BuildStatus.FAIL);
  257. environment.setBuildOutput("Timeout interrupt.");
  258. logger.info("Interrupt buildTask: " + environment.getId());
  259. environmentDAO.save(environment);
  260. }
  261. }
  262. environments = environmentDAO.findAllByDeployStatus(DeployStatus.DEPLOYING);
  263. environments.addAll(environmentDAO.findAllByDeployStatus(DeployStatus.RESTARTING));
  264. if (!CollectionUtils.isEmpty(environments)) {
  265. for (Environment environment : environments) {
  266. if (environment.getDeployStartTime().isBefore(startTime)) {
  267. environment.setDeployStatus(DeployStatus.FAIL);
  268. environment.setDeployOutput("Timeout interrupt.");
  269. logger.info("Interrupt deployTask: " + environment.getId());
  270. environmentDAO.save(environment);
  271. } else {
  272. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(environment.getAppId()), String.valueOf(environment.getId()));
  273. List<Deployment> deployments = deploymentApi.getByCondition(K8sObjectRequest
  274. .builder()
  275. .name(labelValue)
  276. .namespace(applicationProperties.getDeploymentNamespace())
  277. .build());
  278. List<Pod> pods = podApi.getByCondition(K8sObjectRequest
  279. .builder()
  280. .labels(Collections.singletonMap(ResourceLabel.RESOURCE.getCode(), labelValue))
  281. .namespace(applicationProperties.getDeploymentNamespace())
  282. .build());
  283. // 优先级先看pod,信息比较多
  284. if (!CollectionUtils.isEmpty(pods)) {
  285. V1PodStatus status = pods.get(0).getPodStatus();
  286. List<V1PodCondition> conditions = status.getConditions() == null ? Collections.emptyList() : status.getConditions();
  287. Collections.sort(conditions, Comparator.comparingLong(c -> ((V1PodCondition)c).getLastTransitionTime().getMillis()).reversed());
  288. String deployOutput = "Type\tStatus\tMessage\tReason\tTime\n";
  289. deployOutput += conditions.stream().map(condition -> {
  290. return condition.getType() + "\t" + condition.getStatus() + "\t" + condition.getMessage() + "\t" + condition.getReason() + "\t" + condition.getLastTransitionTime().toString();
  291. }).collect(Collectors.joining("\n"));
  292. switch (status.getPhase()) {
  293. case "Succeeded":
  294. case "Running":
  295. if (conditions.get(0) != null && conditions.get(0).getStatus().equals("False")) {
  296. environment.setDeployStatus(DeployStatus.FAIL);
  297. } else {
  298. environment.setDeployStatus(DeployStatus.SUCCESS);
  299. }
  300. break;
  301. case "Pending":
  302. environment.setDeployStatus(DeployStatus.DEPLOYING);
  303. break;
  304. case "Failed":
  305. case "Unknown":
  306. default:
  307. environment.setDeployStatus(DeployStatus.FAIL);
  308. break;
  309. }
  310. environment.setDeployOutput(deployOutput);
  311. environmentDAO.save(environment);
  312. }
  313. }
  314. }
  315. }
  316. }
  317. void buildAsync(Integer id) {
  318. Environment result = environmentDAO.findById(id).get();
  319. try {
  320. Config config = configDAO.findByConfigBelongsAndAndEntityId(ConfigBelongsToType.ENVIRONMENT, id);
  321. Application application = applicationDAO.findById(result.getAppId()).get();
  322. ConfigVO.ConfigContent configContent = ConfigConverter.convertToVO(config).getConfig();
  323. String labelKey = ResourceLabel.RESOURCE.getCode();
  324. String labelValue = ResourceLabel.RESOURCE.getGenerator().gen("environment", String.valueOf(result.getAppId()), String.valueOf(result.getId()));
  325. Map<String, String> labelsMap = Collections.singletonMap(labelKey, labelValue);
  326. String deploymentNamespace = applicationProperties.getDeploymentNamespace();
  327. String deploymentHost = applicationProperties.getDeploymentHost();
  328. String imageName = result.getBuildTypeValue();
  329. // 1. 有git从git拿到地址进行构建并push
  330. if (result.getBuildType() == BuildType.FROM_BRANCH_OR_COMMIT) {
  331. try {
  332. Path directory = Files.createTempDirectory("seecoder-paas-");
  333. Git git = gitApi.clone(application.getGitUrl(), directory.toString());
  334. try {
  335. // 尝试切换分支
  336. git.checkout().setCreateBranch(false).setName(result.getBuildTypeValue()).call();
  337. } catch (Exception gitException) {
  338. // 否则尝试commit
  339. git.reset().setMode(ResetCommand.ResetType.HARD).setRef(result.getBuildTypeValue()).call();
  340. }
  341. if (Files.exists(Paths.get(directory.toString(), "Dockerfile"))) {
  342. String imageTag = String.valueOf(new Date().getTime());
  343. StringBuilder sb = new StringBuilder();
  344. final int DEFAULT_BUFFER_LENGTH = 200;
  345. final long MAX_BUFFER_SIZE = 1 << 18;
  346. dockerApi.buildAndPush(directory.toString(), "seecoder-paas-" + labelValue, imageTag, new ProgressHandler() {
  347. @Override
  348. public void progress(ProgressMessage message) throws DockerException {
  349. String value = message.stream();
  350. if (value == null) {
  351. value = "";
  352. }
  353. if (!StringUtils.isEmpty(message.error())) {
  354. value += "\n" + message.error();
  355. }
  356. sb.append(value);
  357. if (sb.length() > DEFAULT_BUFFER_LENGTH) {
  358. result.setBuildOutput(result.getBuildOutput() + sb.toString());
  359. if (result.getBuildOutput().length() > MAX_BUFFER_SIZE) {
  360. result.setBuildOutput(result.getBuildOutput().substring((int) (result.getBuildOutput().length() - MAX_BUFFER_SIZE)));
  361. }
  362. sb.setLength(0);
  363. environmentDAO.save(result);
  364. }
  365. }
  366. });
  367. if (sb.length() > 0) {
  368. result.setBuildOutput(result.getBuildOutput() + sb.toString());
  369. }
  370. imageName = applicationProperties.getK8s().getImageRegistry() + "/seecoder-paas-" + labelValue + ":" + imageTag;
  371. result.setBuildStatus(BuildStatus.SUCCESS);
  372. result.setDeployStatus(DeployStatus.DEPLOYING);
  373. result.setDeployStartTime(LocalDateTime.now());
  374. result.setImage(imageName);
  375. environmentDAO.save(result);
  376. } else {
  377. result.setBuildStatus(BuildStatus.FAIL);
  378. result.setBuildOutput("找不到构建文件Dockerfile!");
  379. environmentDAO.save(result);
  380. throw new ServiceException("102", "找不到构建文件Dockerfile!");
  381. }
  382. } catch (Exception e) {
  383. result.setBuildStatus(BuildStatus.FAIL);
  384. if (!e.getLocalizedMessage().contains("Could not acquire image ID or digest following build")) {
  385. result.setBuildOutput(e.getLocalizedMessage());
  386. }
  387. environmentDAO.save(result);
  388. LoggerUtil.error(logger, e, "构建失败!gitUrl={}, branchOrCommit={}", application.getGitUrl(), result.getBuildTypeValue());
  389. return;
  390. }
  391. } else if (result.getBuildType() == BuildType.FROM_IMAGE) {
  392. result.setBuildStatus(BuildStatus.SUCCESS);
  393. result.setBuildOutput("镜像构建无需部署");
  394. result.setDeployStatus(DeployStatus.DEPLOYING);
  395. result.setDeployStartTime(LocalDateTime.now());
  396. result.setImage(imageName);
  397. environmentDAO.save(result);
  398. }
  399. String[] ports = (configContent.getServicePort() == null ? "" : configContent.getServicePort()).split(",");
  400. if (!StringUtils.isEmpty(configContent.getHostPrefix())) {
  401. for (String port : ports) {
  402. // ingress
  403. List<Ingress> ingresses = ingressApi.getByCondition(K8sObjectRequest.builder()
  404. .name(labelValue + "-" + port)
  405. .namespace(deploymentNamespace).labels(labelsMap).build());
  406. Ingress ingress = new Ingress();
  407. ingress.setNamespace(deploymentNamespace);
  408. ingress.setName(labelValue + "-" + port);
  409. ingress.setLabels(labelsMap);
  410. if (configContent.getIngressAnnotations() != null && configContent.getIngressAnnotations().get(port) != null) {
  411. ingress.setAnnotations(configContent.getIngressAnnotations().get(port));
  412. }
  413. String ingressHost = configContent.getHostPrefix() + "-" + port + "." + deploymentHost;
  414. if (configContent.getIngressPort() != null) {
  415. if (port.equals(configContent.getIngressPort())) {
  416. ingressHost = configContent.getHostPrefix() + "." + deploymentHost;
  417. }
  418. } else {
  419. if (ports.length == 1 || port.equals("80")) {
  420. ingressHost = configContent.getHostPrefix() + "." + deploymentHost;
  421. }
  422. }
  423. ingress.setAnnotation(Ingress.REWRITE_ANNOTATION, Ingress.DEFAULT_REWRITE_PATH);
  424. ingress.setHttpRules(Collections.singletonList(
  425. Ingress.IngressRule.builder().host(ingressHost).ruleValues(
  426. Collections.singleton(Ingress.IngressPath.builder()
  427. .path("/")
  428. .serviceName(labelValue)
  429. .port(Integer.parseInt(port))
  430. .build())
  431. ).build()
  432. ));
  433. if (CollectionUtils.isEmpty(ingresses) || ingresses.size() != 1) {
  434. ingressApi.create(ingress);
  435. } else {
  436. ingressApi.update(ingress);
  437. }
  438. }
  439. }
  440. // service
  441. List<Service> svcs = serviceApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
  442. Service svc = Service.builder().name(labelValue).labels(labelsMap).namespace(deploymentNamespace).selectors(labelsMap).servicePorts(
  443. Arrays.stream(ports).map(port ->
  444. ServicePort.builder()
  445. .name(labelValue + "-" + port)
  446. .port(Integer.parseInt(port))
  447. .targetPort(Integer.parseInt(port))
  448. .build()).collect(Collectors.toSet())).build();
  449. if (CollectionUtils.isEmpty(svcs) || svcs.size() != 1) {
  450. serviceApi.create(svc);
  451. } else {
  452. serviceApi.update(svc);
  453. }
  454. // 检查secret,没有则创建
  455. SecretVO secretVO = secretApi.getSecretByName(deploymentNamespace, DEFAULT_IMAGE_PULL_SECRET_NAME);
  456. if (secretVO == null) {
  457. secretApi.createPrivateRegistrySecret(
  458. deploymentNamespace,
  459. DEFAULT_IMAGE_PULL_SECRET_NAME,
  460. applicationProperties.getK8s().getImageRegistry(),
  461. applicationProperties.getDocker().getRegistryUsername(),
  462. applicationProperties.getDocker().getRegistryPassword()
  463. );
  464. }
  465. // configMap
  466. List<ConfigMap> configMaps = configMapApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
  467. ConfigMap configMap = ConfigMap.builder().data(configContent.getMountsConfigMaps()).build();
  468. configMap.setName(labelValue);
  469. configMap.setNamespace(deploymentNamespace);
  470. configMap.setLabel(labelKey, labelValue);
  471. if (CollectionUtils.isEmpty(configMaps) || configMaps.size() != 1) {
  472. configMapApi.create(configMap);
  473. } else {
  474. configMapApi.update(configMap);
  475. }
  476. // deployment
  477. List<Deployment> deployments = deploymentApi.getByCondition(K8sObjectRequest.builder().namespace(deploymentNamespace).labels(labelsMap).build());
  478. List<V1Volume> volumes = new ArrayList<>();
  479. List<Container.VolumeMount> volumeMounts = new ArrayList<>();
  480. if (!CollectionUtils.isEmpty(configMap.getData())) {
  481. volumes.add(new V1VolumeBuilder().withName(labelValue).withConfigMap(
  482. new V1ConfigMapVolumeSourceBuilder().withName(labelValue).withItems(
  483. configMap.getData() == null ? null :
  484. configMap.getData().keySet().stream()
  485. .map(path -> {
  486. String relativePath = path.replaceAll(ConfigVO.ConfigContent.PATH_DELIMITER, "/");
  487. String[] arrs = relativePath.split("/");
  488. return new V1KeyToPathBuilder()
  489. .withKey(path)
  490. .withPath(
  491. arrs[arrs.length - 1]
  492. )
  493. .build();
  494. })
  495. .collect(Collectors.toList())
  496. ).build()
  497. ).build());
  498. volumeMounts.addAll(configMap.getData().keySet().stream()
  499. .map(path -> {
  500. String relativePath = path.replaceAll(ConfigVO.ConfigContent.PATH_DELIMITER, "/");
  501. String[] arrs = relativePath.split("/");
  502. return Container.VolumeMount.builder()
  503. .name(labelValue)
  504. .mountPath(relativePath)
  505. .subPath(arrs[arrs.length - 1])
  506. .build();
  507. })
  508. .collect(Collectors.toList()));
  509. }
  510. if (!CollectionUtils.isEmpty(configContent.getHostPath())) {
  511. for (Map.Entry<String, String> entry : configContent.getHostPath().entrySet()) {
  512. String name = labelValue + "-" + entry.getKey().replaceAll("[/.]", "-");
  513. volumes.add(new V1VolumeBuilder().withName(name).withHostPath(
  514. new V1HostPathVolumeSourceBuilder().withPath(entry.getKey()).build()
  515. ).build());
  516. volumeMounts.add(Container.VolumeMount.builder()
  517. .name(name)
  518. .mountPath(entry.getValue())
  519. .build());
  520. }
  521. }
  522. if (!CollectionUtils.isEmpty(configContent.getPvcPaths())) {
  523. for (Map.Entry<String, String> entry : configContent.getPvcPaths().entrySet()) {
  524. String name = labelValue + "-" + entry.getKey();
  525. volumes.add(new V1VolumeBuilder().withName(name).withPersistentVolumeClaim(
  526. new V1PersistentVolumeClaimVolumeSourceBuilder().withClaimName("a" + result.getAppId() + "-" + entry.getKey()).build()
  527. ).build());
  528. volumeMounts.add(Container.VolumeMount.builder()
  529. .name(name)
  530. .mountPath(entry.getValue())
  531. .build());
  532. }
  533. }
  534. Deployment deployment = Deployment.builder()
  535. .imagePullSecrets(Collections.singletonList(DEFAULT_IMAGE_PULL_SECRET_NAME))
  536. .timeout(Deployment.DEFAULT_PROGRESS_DEADLINE_SECONDS)
  537. .revisionHistoryLimit(Deployment.DEFAULT_REVISION_HISTORY_LIMIT)
  538. .replicas(configContent.getReplicas() == null ? 1 : configContent.getReplicas())
  539. .nodeSelectors(configContent.getNodeIp() == null ? null : Collections.singletonMap("paas.seecoder.cn/ip", configContent.getNodeIp()))
  540. .volumes(volumes)
  541. .containers(Collections.singletonList(
  542. Container.builder()
  543. .env(configContent.getEnvs())
  544. .ports(
  545. Arrays.stream(ports).map(port ->
  546. ContainerPort.builder()
  547. .name(application.getId() + "a" + result.getId() + "e" + port)
  548. .port(Integer.parseInt(port))
  549. .protocol("TCP")
  550. .build()).collect(Collectors.toSet())
  551. )
  552. .args(StringUtils.isEmpty(configContent.getRunArgs()) ? null : Arrays.asList(configContent.getRunArgs().split(" ")))
  553. .command(StringUtils.isEmpty(configContent.getRunCommands()) ? null : Arrays.asList(configContent.getRunCommands().split(" ")))
  554. .name(labelValue)
  555. .image(imageName)
  556. .volumeMounts(volumeMounts)
  557. .build()))
  558. .build();
  559. deployment.setName(labelValue);
  560. deployment.setNamespace(deploymentNamespace);
  561. deployment.setLabel(labelKey, labelValue);
  562. if (CollectionUtils.isEmpty(deployments) || deployments.size() != 1) {
  563. deploymentApi.create(deployment);
  564. } else {
  565. deploymentApi.update(deployment);
  566. }
  567. } catch (Exception e) {
  568. if (result.getBuildStatus() == BuildStatus.BUILDING) {
  569. result.setBuildStatus(BuildStatus.FAIL);
  570. result.setBuildOutput(e.getLocalizedMessage());
  571. }
  572. if (result.getDeployStatus() == DeployStatus.DEPLOYING) {
  573. result.setDeployStatus(DeployStatus.FAIL);
  574. result.setDeployOutput(e.getLocalizedMessage());
  575. }
  576. environmentDAO.save(result);
  577. LoggerUtil.error(logger, e, "流程失败!");
  578. return;
  579. }
  580. }
  581. }