AKS中重写规则踩坑小记录

前言

最近在做标准产品在不同云平台中的部署验证,有幸体验了一下微软的Azure。负责采购的运维部门这次采用了Application Gateway来搭配AKS(Azure Kubernetes Service)对外暴露服务,正好借着这个机会来体验一下Application Gateway

应用场景

  1. 域名api.demo.com指向Application Gateway的IP地址
  2. AKS内部2个Service, gateway-servicebackend-service分别需要通过Application Gateway对外暴露。
  3. /gateway/指向gateway-service, 然后/backend/指向backend-service。而且两个Service都没有context-path,所以需要做一个Rewrite重写URI到Service的根目录上。

定义重写集

打开AKS对应的应用程序网关设置 > 重写。选择添加重写集。在1. 名称和关联这个Tab上只需要填写名称这项即可(名称后面在做ingress时需要使用), 关联的传递规则不需要选择。2. 重写规则配置里添加一个重写规则,然后填上重写规则的名称,并添加条件(默认新建重写规则时,只会生成操作,不会生成条件)

条件做如下设置

  • 要检查的变量类型 : 服务器变量
  • 服务器变量: request_uri
  • 区分大小写:
  • 运算符: 等号(=)
  • 要匹配的模式: /(gateway|backend)/?(.*)

操作做如下设置

  • 重写类型: URL
  • 操作类型: 设置
  • 组件: URL路径和URL查询字符串
  • URL路径值: /{var_request_uri_2}
  • 重新计算路径映射: 不选中
  • URL查询字符串值: 留空不设值

特殊说明

操作里的URL路径值不能使用正则表达式GROUP替换组,例如$1$2之类的。Azure自己定义了一套对应的替换组命名规则。具体可以参考这个网页使用应用程序网关重写 HTTP 标头和 URL

另外一个需要注意一点,如果在条件里选择了服务器变量request_uri的时候,注意这个request_uri是完整的原始请求URI(携带了查询参数)。例如: 在请求http://api.demo.com/gateway/search?foo=bar&hello=world中,request_uri的值将为/gateway/search?foo=bar&hello=world。由于request_uri里包含了查询参数,所以在操作组件中建议勾选URL路径和URL查询字符串。如果只选择URL路径的情况下可能出现无法预期的错误。以我们上述的配置来说明。

对象URL: http://api.demo.com/gateway/search?foo=bar&hello=world

组件 URL路径和URL查询字符串 URL路径
结果 /search?foo=bar&hello=world /search?foo=bar&hello=world?foo=bar&hello=world

ACK的Ingress设置

当选择了Application Gateway作为对外暴露Service的方式时,Kubernetes集群里(kube-system命名空间里)多一个Application Gateway Ingress Controller(Azure工单时通常会简称为agic)的Deployment,所以对外暴露服务时可以像传统nginx ingress controller一样添加一个Ingress对象即可(甚至配置也和ngic大致相同,只是多了2个annotations)

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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# 这里指定重写规则集(不是重写规则的名字)
appgw.ingress.kubernetes.io/rewrite-rule-set: rule-backend
# 指定说明你这里ingress的类型是agic
kubernetes.io/ingress.class: azure/application-gateway
name: backend-ingress
namespace: default
spec:
rules:
- host: api.demo.com
http:
paths:
- backend:
service:
name: gateway-service
port:
number: 8080
path: /gateway/
pathType: Prefix
- backend:
service:
name: backend-service
port:
number: 8080
path: /backend/
pathType: Prefix

总结

由于微软云这块文档有部分缺失,导致在配置这块花了一点时间去排查,甚至开了工单。总结下来Ingress的配置主要是根据请求路径路由到对应的Service,重写规则集才是实际负责根据正则来进行匹配重写。

Spring Cloud Kubernetes环境下使用Jasypt

前言

最近半年着手开始做了基于微服务的中台项目,整个项目的技术栈采用的是Java + Spring Cloud + Kubernetes + Istio

业务开放上还是相当顺利的。但是在安全审核上,运维组提出了一个简易。现在项目一些敏感配置,例如MySQL用户的密码,Redis的密码等现在都是明文保存在Kubernetes的ConfigMap中的(是的,我们并没有Nacos作为微服务的配置中心)。这样可能存在安全隐患。

首次尝试

既然有问题,那就解决问题。要给配置文件中的属性项目加密很简单,稍微Google一下,就有现成的方案了。

现在比较常用的解决方案就是集成Jasypt,然后通过jasypt-spring-boot-starter来融合进Spring。

POM包加入jasypt-spring-boot-starter

1
2
3
4
5
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>

Dockerfile中增加java参数

1
2
...
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar app.jar --jasypt.encryptor.password=helloworld $PARAMS"]

在ConfigMap中添加加密属性

1
2
3
4
5
6
7
apiVersion: v1
kind: ConfigMap
metadata:
name: demo
data:
application.yaml: |-
test2: ENC(94Y7Ds3+RKraxQQlura9sDx+9yF0zDLMGMwi2TjyCFZOkkHfreRFSb6fxbyvCKs7)

利用actuator接口测试

management.endpoints.web.exposure.include属性中增加env,这样我们就可以通过调用/actuator/env来查看一下env接口返回的整个Spring 容器中所有的PropertySource。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
...
"propertySources": [
{
"name": "bootstrapProperties-configmap.demo.default",
"properties": {
"test2": {
"value": "Hello,world"
}
}
}
...
]
}

OK, 这下配置项已经加密了。问题解决了。

但是…

新的问题

自从项目集成了Jayspt以后,出现了一个奇怪的问题。每次项目试图通过修改ConfigMap的配置文件,然后试图通过spring-cloud-starter-kubernetes-fabric8-config来做自动Reload,都失败了。然而查阅应用日志,并没有出现任何异常。无奈只能打开spring-cloudjasypt-spring-bootDEBUG日志。

进过几天对日志和两边源代码的分析。终于找到了原因

原因

在Spring Boot启动时jasypt-spring-boot会将下面6种配置(并不仅限与这6种配置文件)

  • Classpath下的application.yaml
  • Classpath下的bootstrap.yaml
  • 集群里名称为${spring.cloud.kubernetes.config.name}的ConfigMap
  • 集群里名称为${spring.cloud.kubernetes.config.name}-kubernetes的ConfigMap
  • Java启动参数
  • 环境变量

转换成jasypt-spring-boot自己的PropertySource实现类EncryptableMapPropertySourceWrapper

但是如果使用Kubernetes的ConfigMap来作微服务配置中心的时候,Spring Cloud会在ConfigurationChangeDetector中查找配置类org.springframework.cloud.bootstrap.config.BootstrapPropertySource, 并依据BootstrapPropertySource的类型来判断容器内的配置与集群中ConfigMap里的配置是否有差异,来触发配置reload。

由于jasypt-spring-boot已经将所有的配置文件转型成了EncryptableMapPropertySourceWrapper, 所以ConfigurationChangeDetector无法找到BootstrapPropertySource所以会一直任务ConfigMap的里的配置没有变化,导致整个Reload失效(无论是使用polling还是event方式)

解决问题

为了保证ConfigMap变化后自动Reload的功能,所以jasypt-spring-boot不能把BootstrapPropertySource转换成EncryptableMapPropertySourceWrapper

所以我们需要设置jasypt.encryptor.skip-property-sources配置项, Classpath中的application.yaml需要增加配置

1
2
3
jasypt:
encryptor:
skip-property-sources: org.springframework.cloud.bootstrap.config.BootstrapPropertySource

skip-property-sources配置项配置后,加密项目就不能配置在ConfigMap里了,毕竟已经被我们忽略了。那么我们只能另外找一个PropertySource来存放加密项目了。

Classpath中的两个Yaml由于编译时会被Maven打包进Jar文件,会牵涉多个CI/CD多个流程显然不合适,启动参数配置项的也要影响到Docker镜像制作这个流程。所以判断下来最适合的PropertySource就是环境变量了。

环境变量增加加密项

在Kubernetes的部署Yaml中,添加加密数据项application.test.str

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demo
name: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: application.test.str
value: >-
ENC(94Y7Ds3+RKraxQQlura9sDx+9yF0zDLMGMwi2TjyCFZOkkHfreRFSb6fxbyvCKs7)
....

如果需要更加严密的加密方针的话,我们可以把环境变量的内容放进Kubernetes的Secrets中。

在ConfigMap中引用application.test.str

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
name: demo
data:
application.yaml: |-
test2: ENC(94Y7Ds3+RKraxQQlura9sDx+9yF0zDLMGMwi2TjyCFZOkkHfreRFSb6fxbyvCKs7)
test3: ${application.test.str}

通过actuator接口来测试

通过actuator\env接口来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
...
"propertySources": [
{
"name": "bootstrapProperties-configmap.demo.default",
"properties": {
"test2": {
"value": "ENC(94Y7Ds3+RKraxQQlura9sDx+9yF0zDLMGMwi2TjyCFZOkkHfreRFSb6fxbyvCKs7)"
},
"test3": {
"value": "Hello,world"
}
}
}
...
]
}

这样ConfigMap中的配置项test3就可以通过环境变量引用并使用加密配置项了。同时修改ConfigMap依然可以触发auto reload了。这下终于算是解决了。

重学Java (一) 泛型

1. 前言

泛型编程自从 Java 5.0 中引入后已经超过15个年头了。对于现在的 Java 码农来说熟练使用泛型编程已经是家常便饭的事情了。所以本文就在不对泛型的基础使用在做说明了。 如果你还不会使用泛型的话,可以参考下面两个链接

这篇文章就简答聊一下,我实际在开发工作中很少用的到泛型方法这个知识点,以及在实际项目中有哪些东西会使用到泛型。

2. 泛型方法

在阅读代码的时候我们经常会看到下面这样的方法 (这段代码摘自 java.util.AbstractCollection)

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
 public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();

for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // fewer elements than expected
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
r[i] = (T)it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
}

那么 pulic 关键字后面的那个 <T> 就是用来标记这个方法是一个泛型方法。 那什么是泛型方法呢。

官方的解释是这样的

1
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

通俗点来将就是将一个方法泛型化,让一个普通的类的某一个方法具有泛型功能。 如果在一个泛型类中增加一个泛型方法,那这个泛型方法就可以有一套独立于这个类的泛型类型。

通过一个简单的例子, 我们来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* GenericClass 这个泛型类是一个简单的套皮的 HashMap
*/
public class GenericClass<K, V> {

private Map<K, V> map = new HashMap<>();

public V put(K key, V value) {
return map.put(key, value);
}

public V get(K key) {
return map.get(key);
}

// 泛型方法 genericMethod 可以接受一个全新的、作用域只限本函数的泛型类型T
public <T> T genericMethod(T t) {
return t;
}

}

实际使用起来

1
2
3
4
5
6
GenericClass<String, Integer> map = new GenericClass<>();
// put 和 get 方法的参数必须使用定义时指定的 String 和 Integer
System.out.println(map.put("One", 1));
System.out.println(map.get("One"));
// 泛型方法 genericMethod 就可以接受一个 String 和 Integer 以外的类型
System.out.println(map.genericMethod(new Double(1.0)).getClass());

我们再来看看 JDK 中使用到泛型方法的例子。我们最常使用的泛型容器 ArrayList 中有个 toArray 方法。JDK 在它的实现中就提供了两个版本,其中一个就是泛型方法的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 这是一个普通版本,返回一个Object的数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

// 这是一个泛型方法的版本,将容器里存储的元素输出到 T[] 数组中。 其中 T 必须是 E 的父类,否则 System.arraycopy 会抛出 ArrayStoreException 异常
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
}

泛型方法总体上来说就是可以给与现有的方法实现上,增加一个更加灵活的实现可能。

3. 实战应用

在实际的项目中,对于泛型的使用,除了像倾倒垃圾一样往泛型容易里塞各种 java bean 和其他泛型对象。还能怎么使用泛型呢?

我们在实际的一些项目中,会对数据库中的一些表(多数时候是全部)先实现 CRUD (Create, Read, Update, Delete)的操作,并从这些操作中延伸出一些简单的 REST 风格的 WebAPI 接口,然后才会根据实际业务需要实现一些更复杂的业务接口。

大体上会是下面这个样子。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

// 这是一个简单的 Entity 对象
// 通常现在的 Java 应用都会使用到 Lombok 和 Spring Boot
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
private String username;
private String password;
}

// 然后这个是 DAO 接口继承自 spring-data 的 JpaRepository
public interface UserDao extends JpaRepository<User, Long> {
}

// 在来是一个访问 User 资源的 Service 和他的实现
public interface UserService {
List<User> findAll();
Optional<User> findById(Long id);
User save (User user)
void deleteById(Long id);
}

@Service
public class UserSerivceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

@Override
public List<User> findAll() {
return this.dao.findAll();
}

@Override
public Optional<User> findById(Long id) {
return this.dao.findById(id);
}

@Override
public User save(User user) {
return this.dao.save(user);
}

@Override
public void deleteById(Long id) {
this.dao.deleteById(id);
}
}

// 最后就是 WebAPI 的接口了
@RestController
@RequestMapping("/user/")
public class UserController{
private UserService userService;
public UserController(userService userService) {
this.userService = userService;
}

@GetMapping
@ResponseBody
public List<User> fetch() {
return this.userService.findAll();
}

@GetMapping("{id}")
@ResponseBody
public User get(@PathVariable("id") Long id) {
// 由于是示例这里就不考虑没有数据的情况了
return this.userService.findById(id).get();
}

@PostMapping
@ResponseBody
public User create(@RequestBody User user) {
return this.userService.save(user);
}

@PutMapping("{id}")
@ResponseBody
public User update(@RequestBody User user) {
return this.userService.save(user);
}

@DeleteMapping("{id}")
@ResponseBody
public User delete(@PathVariable("id") Long id) {
User user = this.userService.findById(id);
this.userService.deleteById(id);
return user;
}
}

大致一个表的一套相关接口就是这个样子的。如果你的数据库中有大量表的话,而且每个表都需要提供 REST 风格的 WebAPI 接口的话,那么这将是一个相当枯燥的而又及其容易出错的工作。

为了不让这项枯燥而又容易犯错的工作占去我们宝贵的私人时间,我们可以通过泛型和继承的技巧来重构从 Service 层到 Controller 的这段代码(感谢 spring-data 提供了 JpaRepository, 让我们不至于从 DAO 层重构)

3.1 Service 层的重构

首先是 Service 接口的重构,我们 Service 层接口就是定义了一组 CRUD 的操作,我们可以将这组 CRUD 操作抽象到一个父接口,然后所有 Service 层的接口都将继承自这个父接口。而接口中出现的 Entity 和主键的类型(上例中 User 的主键 id 的类型是 Long)就可以用泛型来展现。

1
2
3
4
5
6
7
8
9
10
11
// 这里泛型表示 E 来指代 Entity, ID 用来指代 Entity 主键的类型
public interface ICrudService<E, ID> {
List<E> findAll();
Optional<E> findById(ID id);
E save(E e);
void deleteById(ID id);
}

// 然后 Service 层的接口,就可以简化成这样
public interface UserService extends ICrudService<User, Long> {
}

同样 Service 层的实现也可以使用相似的方法具体实现可以抽象到一个基类中。

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
// 相比 ICrudService 这里有多了一个泛型 T 来代表 Entity 对应的 DAO, 我们的每一个 DAO 都继承自
// spring-data 的 JpaRepository 所以,这里可以使用到泛型的边界
public abstract class AbstractCrudService<T extends JpaRepository<E, ID>, E, ID> {
private T dao;
public AbstractCrudService(T dao) {
this.dao = dao;
}

public List<E> findAll() {
return this.dao.findAll();
}

public Optional<E> findById(ID id) {
return this.dao.findById(id);
}

public E save(E e) {
return this.dao.save(e);
}

public void deleteById(ID id) {
this.dao.deleteById(id);
}
}

// 那 Service 的实现类可以简化成这样
@Service
public class UserServiceImpl extends AbstractCrudService<UserDao, User, Long> implements UserService {
public UserServiceImpl(UserDao dao) {
supper(dao);
}
}

同样我们可以通过相同的方法来对 Controller 层进行重构

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
49
// Controller 层的基类
public abstract class AbstractCrudController<T extends ICrudService<E, ID>, E, ID> {
private T service;
public AbstractCrudController(T service) {
this.service = service;
}

@GetMapping
@ResponseBody
public List<E> fetch() {
return this.service.findAll();
}

@GetMapping("{id}")
@ResponseBody
public E get(@PathVariable("id") ID id) {
// 由于是示例这里就不考虑没有数据的情况了
return this.service.findById(id).get();
}

@PostMapping
@ResponseBody
public E create(@RequestBody E e) {
return this.service.save(e);
}

@PutMapping("{id}")
@ResponseBody
public E update(@RequestBody E e) {
return this.service.save(e);
}

@DeleteMapping("{id}")
@ResponseBody
public E delete(@PathVariable("id") ID id) {
E e = this.service.findById(id).get();
this.service.deleteById(id);
return e;
}
}

// 具体的 WebAPI
@RestController
@RequestMapping("/user/")
public class UserController extends AbstractCrudController<UserService, User, Long> {
public UserController(UserService service) {
super(service);
}
}

经过重构可以消减掉 Servcie 和 Controller 中的大量重复代码,使代码更容易维护了。

4. 结尾

关于泛型就简单的说这些了,泛型作为 Java 日常开发中一个常用的知识点,其实还有很多知识点可以供我们挖掘,奈何本人才疏学浅,这么多年工作下来,只积累出来这么点内容。

文末放上示例代码的代码库:

阿里云Kubernetes上线踩坑记

1
2
Update:
2020-04-08 增加istio-ingressgateway高可用的设置

最近公司因为项目需要,在阿里云上部署了一个Kubernetes集群。虽然阿里云的文档说的还算细致,但是还是有些没有明确说明的细节。

1. 购买篇

申请项目预算的时候,只考虑到Worker节点,1个SLB节点以及域名和证书的预算。但是实际购买的时候发现还有许多额外的开销。

1.1 SNAT

这个和EIP一并购买,可以方便通过公网使用kubectl访问集群。关于SNAT网关至今不是很明白需要购买这个服务的意义何在,只是为了一个EIP来访问集群吗?

1.2 Ingress

这个选上了后,阿里云会给你买个SLB而且还是带公网访问的,如果你后期考虑使用Istio的话,建议你集群创建后,直接停止这个SLB,以免产生额外的费用。

1.3 日志服务

通过阿里云的日志服务来收集应用的的日志,挺好用的。但是另外收费,如果有能力的自建日志服务的可不购买。

2. Istio

阿里云的Kubernetes集群完美集成了Istio,根据向导就能很简单的部署成功。

2.1 额外的SLB

Istio的Gateway 需要绑定一个新的SLB,和Ingress的SLB不能是同一个,又是一笔额外的开销

2.2 集群外访问

这个在阿里云的Istio FAQ中有提到,按照指导很容易解决

2.2 SLB的443监听

为了方便443端口的证书绑定,我们直接删除了SLB上原有的443监听(TCP协议), 重新建了一个443监听(HTTPS协议),指向和80端口同样的虚拟服务器组。但是设置健康检查时一直出错,经过排查发现SLB健康检查发送的请求协议是HTTP 1.0的,Istio的envoy直接反悔了426(Upgrade Required)这个状态码,所以我们无奈只能把健康检查的检查返回状态改为http_4xx,这样就能通过SLB的健康检查了。

2.3 istio-ingressgateway的高可用

istio-ingressgateway要达成高可用,只需要增加通过伸缩POD就可以实现,于istio-ingressgateway对应的SLB中的虚拟服务器组也会自动增加,完全不需要进行额外的手动设定。

由于istio-ingressgateway中挂载了HPAHorizontalPodAutoscaler(简称HPA),通常三节点的集群中最小POD数只有1台,在3节点的集群中,要实现高可用,需要手动修改HPA,增加最小POD数。


基本上现在遇到了这些坑,再有在总结吧。

在ubuntu上从零搭建node.js + nginx + mongodb环境

说到后端开发环境,最有名的莫过于LAMP和LNMP,最近由于node.js的强势崛起,越来越多的后端开发也开始试水node.js了。我最近也因为各种原因,前前后后总够构建了好几台node.js + nginx + mongodb的Linux服务器。

首先关于Linux服务器,比起CentOS来说,我更加喜欢ubuntu一点。所以无论是阿里云还是一些海外的vps服务器上,我也倾向选用ubuntu服务器,本贴也是基于ubuntu服务器里说明的。

1.开始前的一些准备

首先还是需要刷新一下ubuntu的包索引并安装build-essential和libssl-dev这2个包以及curl这个工具。

1
2
3
sudo apt-get update
sudo apt-get install build-essential libssl-dev
sudo apt-get isntall curl

2.安装node.js

关于安装node.js这一点,我不是很推荐使用apt-get 来安装node.js的环境。主要是因为node.js和io.js合并以后,版本迭代速度相当频繁(主要还是因为更多ES6的特性得到了支持)。今后很有可能会有在一台服务器上使用不同版本的node.js的需求。

这里推荐一个管理不同版本node.js的工具:nvm,官网: https://github.com/creationix/nvm  。安装nvm,如果前面你安装了curl的话可以

1
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash

如果没有按照curl的话,也可以使用wget来进行安装

1
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash

然后nvm就会自动安装到home目录下面的.nvm目录里,并会在.bashrc里自动添加nvm的环境变量。为了让环境变量生效,最简单的方法就是通过ssh或是telnet重新连接你的服务器。

安装完nvm后,就可以通过nvm来安装指定版本的node.js了。

1
2
3
4
5
# 列出可以安装的node版本号
nvm ls-remote

# 安装指定版本的node (当前最新版本为v5.7.1, LTS版是v4.3.2)
nvm install v4.3.2

3.安装nginx

由于ubuntu源(尤其是阿里云的源)上的nginx经常不是最新的,如果需要安装最新版本nginx的时候需要手动添加nginx的源。

1
2
3
4
5
6
7
8
9
10
11
# 添加nginx的mainline仓库
cd /tmp/ && wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

# 编辑/etc/apt/sources.list.d/nginx.list 添加下面2行内容,井号不需要
# deb http://nginx.org/packages/mainline/ubuntu/ ubuntu代号 nginx
# deb-src http://nginx.org/packages/mainline/ubuntu/ ubuntu代号 nginx
sudo vi /etc/apt/sources.list.d/nginx.list

# 更新源,并安装nginx
sudo apt-get update && sudo apt-get install nginx

在编辑/etc/apt/sources.list.d/nginx.list的时候需要注意,“ubuntu代号”需要根据ubuntu服务器的版本不同手动调整的,比如14.04是trusty。通过下面的命令可以获取ubuntu的代号。

1
lsb_release -cs

4.安装mongodb

同样和nginx有同样的问题,要安装最新3.2版本的mongodb也需要手动添加ubuntu的源。

1
2
3
4
5
6
7
8
9
10
11
# 导入mongodb的public key
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927

# 生成mongodb的源list
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list

# 更新源
sudo apt-get update

# 安装最新版本的mongodb
sudo apt-get install -y mongodb-org

以上一台node.js + nginx + mongodb的ubuntu服务器就完成了。

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×