当前位置: 首页 > news >正文

DevOps系列文章之 GitlabCICD自动化部署SpringBoot项目

一、概述

本文主要记录如何通过Gitlab CI/CD自动部署SpringBoot项目jar包。

二、前期准备

准备三台 CentOS7服务器,分别部署以下服务:

序号系统IP服务
1CentOS7192.168.56.10Gitlab
2CentOS7192.168.56.11Runner (安装Docker)
3CentOS7192.168.56.12SpringBoot 项目 jar 包(安装jdk、maven等)

上述服务也可以只用一台CentOS7,将所有程序都部署在同一机器上,但是更建议分开部署;

三、总体架构图

 

说明:

  • Gitlab Server 用于部署Gitlab远程仓库,对CPU和内存要求比较高,建议4核CPU,4GB以上内存;
  • Runner Server 用于部署执行.gitlab-ci.yml 文件中定义的 stage(阶段);需要具有访问 Gitlab 仓库的权限,可以下载代码,通过注册方式(gitlab-runner register)实现;
  • Your Laptop Server 用户部署你的应用程序,这里就是SpringBoot的 jar 包,需要提前安装 JDK 和 Maven 并配置好环境变量;

四、环境搭建

1、环境准备(可选)

三台服务器执行以下命令:

1

2

3

yum -y upgrade

yum -y install wget

yum -y install vim

2、Gitlab安装

参考地址:
https://about.gitlab.com/install/#centos-7
CentOS8.1搭建Gitlab服务器详细教程_Linux_脚本之家

(1)安装并配置必要的依赖

1

2

3

4

5

6

7

sudo yum install -y curl policycoreutils-python openssh-server

sudo systemctl enable sshd

sudo systemctl start sshd

sudo firewall-cmd --permanent --add-service=http

sudo firewall-cmd --permanent --add-service=https

sudo systemctl reload firewalld

(2)安装邮件服务

1

2

3

sudo yum install postfix

sudo systemctl enable postfix

sudo systemctl start postfix

(3)添加 gitlab 镜像

参考地址:Index of /gitlab-ce/yum/el7/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

1

wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-13.4.0-ce.0.el7.x86_64.rpm

(4)安装 gitlab 安装命令

1

rpm -i gitlab-ce-13.4.0-ce.0.el7.x86_64.rpm --nodeps --force

安装成功后图片:

 

(5)修改gitlab配置文件指定服务器ip和自定义端口

1

vim  /etc/gitlab/gitlab.rb

(6)重置并启动GitLab

1

2

gitlab-ctl reconfigure

gitlab-ctl restart

提示 "ok: run:"表示启动成功

(7)访问 GitLab页面

如果报502,等待一段时间后再刷新试试,一般1-2分钟左右。

GitLab登录页面

本文设置的账号:root ,新密码:11112222

3、安装 Runner

参考官方文档:https://docs.gitlab.com/runner/install/linux-manually.html#install-gitlab-runner-manually-on-gnulinux 中的 Using binary file Install

(1)下载一个二进制文件

1

sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

(2)修改执行权限

1

sudo chmod a+x /usr/local/bin/gitlab-runner

(3)创建 GitLab CI 用户

1

sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

(4)安装并作为服务运行

1

2

sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner

sudo gitlab-runner start

如果遇到提示 sudo: gitlab-runner: command not found,切换到 root 用户,可以去掉 sudo 执行上面命令。

(5)注册 Runner

参考地址:Registering runners | GitLab
执行 gitlab-runner register 命令:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

[root@localhost bin]# gitlab-runner register

Runtime platform                                    arch=amd64 os=linux pid=21527 revision=4e1f20da version=13.4.0

Running in system-mode.

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):

http://192.168.56.10/

Please enter the gitlab-ci token for this runner:

PwF1sZPX_zsB-xChSKjH

Please enter the gitlab-ci description for this runner:

[localhost.localdomain]: test ci cd desc

Please enter the gitlab-ci tags for this runner (comma separated):

my-tag,other-tag

Registering runner... succeeded                     runner=PwF1sZPX

Please enter the executor: ssh, virtualbox, parallels, shell, docker-ssh, docker+machine, docker-ssh+machine, kubernetes, custom, docker:

docker

Please enter the default Docker image (e.g. ruby:2.6):

maven:3.3.9-jdk-8

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

注:这里选择的docker方式,所以服务器上还需要额外多安装docker
参考:史上最全(全平台)docker安装方法! - 知乎

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

#!/bin/bash

# 移除掉旧的版本

sudo yum remove docker \

                  docker-client \

                  docker-client-latest \

                  docker-common \

                  docker-latest \

                  docker-latest-logrotate \

                  docker-logrotate \

                  docker-selinux \

                  docker-engine-selinux \

                  docker-engine

# 删除所有旧的数据

sudo rm -rf /var/lib/docker

#  安装依赖包

sudo yum install -y yum-utils \

  device-mapper-persistent-data \

  lvm2

# 添加源,使用了阿里云镜像

sudo yum-config-manager \

    --add-repo \

    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 配置缓存

sudo yum makecache fast

# 安装最新稳定版本的docker

sudo yum install -y docker-ce

# 配置镜像加速器

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'

{

  "registry-mirrors": ["http://hub-mirror.c.163.com"]

}

EOF

# 启动docker引擎并设置开机启动

sudo systemctl start docker

sudo systemctl enable docker

# 配置当前用户对docker的执行权限

sudo groupadd docker

sudo gpasswd -a ${USER} docker

sudo systemctl restart docker

这里注册一个全局共享的 Runner(管理员权限,复制服务器地址和 Token),所有项目都可以使用,或者也可以注册项目级别单独的 Runner (进入项目 Runner 设置页面,复制地址和 Token)。

添加GitLab-Runner

注册成功后,Runner 列表可以查看到注册的 Runner

GitLab-Runner添加成功

勾选:Run untagged jobs Indicates whether this runner can pick jobs without tags

GitLab-Runner配置

4、安装应用服务器环境

(1)允许用户远程登录(可选)

1

2

3

4

5

6

7

vi /etc/ssh/sshd_config

修改:

PasswordAuthentication yes                     

PermitRootLogin yes

重启服务:

service sshd restart

(2)安装JDK1.8

(1)下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
(2)解压

1

2

3

tar -zxvf jdk-8u161-linux-x64.tar.gz

重命名:

mv jdk1.8.0_161 java1.8

(3)配置环境变量

1

2

3

4

5

6

7

8

9

10

vi /etc/profile

添加以下内容:

export JAVA_HOME=/usr/local/java1.8

export PATH=$JAVA_HOME/bin:$PATH

export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

保存退出

source /etc/profile

java -version

(3) 安装 Maven3.3.9

(1)下载地址:http://maven.apache.org/download.cgi
(2)解压

1

2

3

4

tar -zxvf apache-maven-3.3.9-bin.tar.gz

重命名:

mv apache-maven-3.3.9 maven-3.3.9

(3)配置环境变量

1

2

3

4

5

6

7

8

9

vi /etc/profile

添加以下内容:

export MAVEN_HOME=/usr/local/maven-3.3.9

export PATH=$MAVEN_HOME/bin:$PATH

保存退出

source /etc/profile

mvn -v

五、创建 SpringBoot 项目

1、使用Gitlab Spring 模板快速创建一个 SpringBoot 项目;

GitLab创建SpringBoot项目

SpringBoot项目配置gitlab-ci.yml.jpg

如果报错,删除pom.xml中的这行

报这个错的话:
[FATAL] Non-resolvable parent POM for com.example:demo:0.0.1-SNAPSHOT: Could not transfer artifact org.springframework.boot:spring-boot-starter-parent:pom:2.0.1.RELEASE from/to central (https://repo.maven.apache.org/maven2): Connect to repo.maven.apache.org:443 [repo.maven.apache.org/151.101.40.215] failed: Connection timed out (Connection timed out) and ‘parent.relativePath' points at wrong local POM @ line 14, column 10

修改版本
1.5.9.RELEASE

2、添加环境变量(登录应用服务器密码)

注: 其中 ssh_password 这个添加到环境变量中,取消勾选 Protect Branch (仅保护分支);修改和添加都是默认勾选,需要取消,否则,其他分支不能读取到该变量;

 

先在应用服务器上创建一个目录,用于上传存放项目 jar 包:

1

mkdir gitlab-project

添加 .gitlab-ci.yml 文件时,可以先再 CI/CD Pipeline 中 的 CI Lint 中检验 .gitlab-ci.yml 文件格式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

# 定义一些变量, 下面各阶段会使用

variables:

  server_ip: 192.168.56.12

  jar_name: demo-0.0.1-SNAPSHOT.jar

  java_path: /usr/local/java1.8/bin

  upload_path: /usr/local/gitlab-project

# 定义执行的各个阶段及顺序

stages:

  - build

  - upload

  - deploy

# 使用 maven 镜像打包项目

maven-build:

  stage: build

  image: maven:3.5.0-jdk-8

  script:

    - mvn package -B -Dmaven.test.skip=true

  cache:

    key: m2-repo

    paths:

      - .m2/repository

  artifacts:

    paths:

      - target/$jar_name

# 上传生成的 jar 包到你的应用服务器,这里使用 ictu/sshpass 这个镜像,是为了使用 sshpass 命令

upload-jar:

  stage: upload

  image: ictu/sshpass

  script:

    - ls -l target/

    - sshpass -p $ssh_password scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no target/$jar_name root@$server_ip:$upload_path/$jar_name

# 启动 SpringBoot jar包

deploy-test:

  stage: deploy

  image: ictu/sshpass

  script:

    - sshpass -p $ssh_password ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@$server_ip "nohup $java_path/java -jar $upload_path/$jar_name >/dev/null 2>&1 &"

这里使用了DockerHub上面的一个公共镜像(ictu/sshpass),主要是想使用启动自带的sshpass命令执行scp和ssh命令。
如果一切顺利的话,就会自动触发 CI/CD ;失败的话查看报错信息,可使用 Debug 模式执行调试命令 。

1

2

3

4

5

[root@localhost gitlab-project]# jps

22119 Jps

22073 demo-0.0.1-SNAPSHOT.jar

[root@localhost gitlab-project]# curl localhost:8080

Spring is here!

 

可能遇到的问题总结:

  1. 权限问题:可以先使用 root 用户看看是不是权限问题导致,如果是的话,提升执行用户的权限;并发问题:这里没有修改 Runner 的并发数,可以修改同时可以进行的任务并发数;其他问题:读取不到配置的环境变量,取消勾选仅保护分支的选项;
  2. 未执行job:没有勾选未配置 tags 也执行选项;

六、总结

使用GitLab自带的CICD功能部署SpringBoot项目非常方便,前期环境搭建可能需要花一点时间学习,但是后期部署项目可以省去很多人为操作失误,对于小型团队来说,Gitlab自带的CICD功能比Jenkins更加简单,总体思想步骤是:

搭建GitLab服务;搭建GitLab Runner服务;搭建应用服务;编写gitlab-ci.yml;
对于SpringBoot项目来说,一般分为:
(1)maven 打包;
(2)上传jar包;
(3)启动jar包;

相关文章:

DevOps系列文章之 GitlabCICD自动化部署SpringBoot项目

一、概述 本文主要记录如何通过Gitlab CI/CD自动部署SpringBoot项目jar包。 二、前期准备 准备三台 CentOS7服务器&#xff0c;分别部署以下服务&#xff1a; 序号系统IP服务1CentOS7192.168.56.10Gitlab2CentOS7192.168.56.11Runner &#xff08;安装Docker&#xff09;3Cen…...

汽车租赁管理系统/汽车租赁网站的设计与实现

摘 要 租赁汽车走进社区&#xff0c;走进生活&#xff0c;成为当今生活中不可缺少的一部分。随着汽车租赁业的发展&#xff0c;加强管理和规范管理司促进汽车租赁业健康发展的重要推动力。汽车租赁业为道路运输车辆一种新的融资服务形式、广大人民群众一种新的出行消费方式和…...

语句覆盖、条件覆盖、判定覆盖、条件-判定覆盖、路径覆盖

白盒测试是结构测试&#xff0c;主要对代码的逻辑进行验证。 逻辑覆盖率&#xff1a;语句覆盖<条件覆盖<判定覆盖<条件-判定覆盖<组合覆盖<路径覆盖 例子 一、语句覆盖 最基础的覆盖&#xff0c;只要每一个执行处理框内的语句都能执行就可&#xff0c;不用关注…...

二进制逻辑运算符

运算的优先级&#xff1a;非>与>或 1.逻辑与&#xff1a;“ ∧ \wedge ∧“&#xff0c;“ ⋅ \cdot ⋅“&#xff0c;and 在逻辑问题中与是所有的都是真结果才是真&#xff0c;比如&#xff1a; 1010101011 1010101011 1010101011和 1010110010 1010110010 1010110010…...

Bug日记-webstorm运行yarn 命令报错

在windows中输入yarn -v正确输出&#xff0c;在webstrom终端中运行yarn命令输出错误 问题&#xff1a;可能是由于 WebStorm 配置问题导致的。 解决方案&#xff1a; 检查 WebStorm 的终端配置&#xff1a;在 WebStorm 中&#xff0c;点击菜单栏的 “File”&#xff08;文件&am…...

C++11并发与多线程笔记(9) async、future、packaged_task、promise

C11并发与多线程笔记&#xff08;9&#xff09; async、future、packaged_task、promise 1、std::async、std::future创建后台任务并返回值2、std::packaged_task&#xff1a;打包任务&#xff0c;把任务包装起来3、std::promise3、小结 1、std::async、std::future创建后台任务…...

Mr. Cappuccino的第63杯咖啡——Spring之AnnotationConfigApplicationContext源码分析

Spring之AnnotationConfigApplicationContext源码分析 源码分析 源码分析 以上一篇文章《Spring之Bean的生命周期》的代码进行源码分析 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig02.class); LifeCycleBe…...

opencv直方图与模板匹配

import cv2 #opencv读取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 直方图 cv2.calcHist(images,channels,mask,histSize,ran…...

Apache Doris 入门教程31:计算节点

需求场景​ 目前Doris是一个典型Share-Nothing的架构, 通过绑定数据和计算资源在同一个节点获得非常好的性能表现. 但随着Doris计算引擎性能持续提高, 越来越多的用户也开始选择使用Doris直接查询数据湖数据. 这类场景是一种Share-Disk场景, 数据往往存储在远端的HDFS/S3上, 计…...

Nacos和GateWay路由转发NotFoundException: 503 SERVICE_UNAVAILABLE “Unable to find

问题再现&#xff1a; 2023-08-15 16:51:16,151 DEBUG [reactor-http-nio-2][CompositeLog.java:147] - [dc73b32c-1] Encoding [{timestampTue Aug 15 16:51:16 CST 2023, path/content/course/list, status503, errorService Unavai (truncated)...] 2023-08-15 16:51:16,17…...

2021年9月全国计算机等级考试真题(二级C语言)

2021年9月全国计算机等级考试真题&#xff08;二级C语言&#xff09; 第1题 下列叙述中正确的是&#xff08; &#xff09;。 A. 算法的复杂度是指算法所处理的数据量 B. 算法的复杂度是指算法程序中指令的数量 C. 算法的复杂度是指算法控制结构的复杂程度 D. 算法的复杂度包…...

串口通讯

USART是全双工同步通讯 在同步通信中&#xff0c;数据信号所传输的内容绝大多数属于有效数据&#xff0c;而异步通信中包含了各种帧的标识符&#xff0c;所以同步通讯的效率更高。但是同步通信对时钟要求苛刻&#xff0c;允许的误差小。而异步通信则允许双方的误差较大 比特率…...

自动拉取 GitHub 仓库更新的脚本

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 由于将 HAUE-CS-WIKI 部署到了我自己的服务器上作为国内镜像站&#xff0c;每次在源站更新后都需要手动拉取镜像站的更新实在是太麻烦了&#xff0c;因此产生了编写该脚本的需求&#xff08; 读者可根据该…...

如何获得Android 14复活节彩蛋

每个新的安卓版本都有隐藏复活节彩蛋的悠久传统&#xff0c;可以追溯到以前&#xff0c;每个版本都以某种甜食命名。安卓14也不例外&#xff0c;但这一次的主题都是围绕太空构建的——还有一个复活节彩蛋。 安卓14复活节彩蛋实际上是一款很酷的小迷你游戏&#xff0c;你可以乘…...

国产32位单片机XL32F001,带1 路 12bit ADC,I2C、SPI、USART 等外设

XL32F001 系列单片机采用高性能的 32 位 ARM Cortex-M0内核&#xff0c;宽电压工作范围的 MCU。嵌入 24KbytesFlash 和 3Kbytes SRAM 存储器&#xff0c;最高工作频率 24MHz。包含多种不同封装类型多款产品。芯片集成 I2C、SPI、USART 等通讯外设&#xff0c;1 路 12bit ADC&am…...

typescript基础之null和undefined

TypeScript是一种基于JavaScript的编程语言&#xff0c;它支持静态类型检查和面向对象的特性。TypeScript中的null和undefined是两种基本类型&#xff0c;它们分别表示空值或未定义的值。在本文中&#xff0c;我将介绍TypeScript中null和undefined的含义、区别、检查方法和使用…...

php_mb_strlen指定扩展

1 中文在utf-字符集下占3个字节,所以计算出来长度为9。 2 可以引入php多字节字符的扩展&#xff0c;默认是没有的&#xff0c;需要自己配置这个函数 3 找到php.ini文件&#xff0c;去掉;extension mbstring的注释&#xff0c;接着重启apache服务 可以看到准确输出的中文的长度…...

利用OpenCV光流算法实现视频特征点跟踪

光流简介 光流&#xff08;optical flow&#xff09;是运动物体在观察成像平面上的像素运动的瞬时速度。光流法是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系&#xff0c;从而计算出相邻帧之间物体的运动信息的一种方法。…...

探索无限创造力的星辰大道,画出想象的浩瀚宇宙!-turtle

介绍 视频教程地址在此&#xff1a;https://www.bilibili.com/video/BV1Pm4y1H7Tb/ 大家好&#xff0c;欢迎来到本视频&#xff01;今天&#xff0c;我们将一同探索Python编程世界中的一个有趣而创意的库——Turtle库。无需专业绘画技能&#xff0c;你就可以轻松地用代码绘制…...

企业数字化转型大数据湖一体化平台项目建设方案PPT

导读&#xff1a;原文《企业数字化转型大数据湖一体化平台项目建设方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以点赞评论转发…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...