从0到1部署一个eggjs应用

November 16, 2018

环境配置

假设已经了解以下应用和服务的使用场景

  • 准备一个eggjs应用
  • gitlab_github_git…
  • Node v8.x.x
  • docker
  • kubernetes

egg

配置文件config

  • config/config.default.js 本地开发环境
  • config/config.test.js 测试环境
  • config/config.prod.js 生产环境

部署时,根据不同的环境变量的设置启用不同的配置文件,一般本地开发而言,npm run dev采用的是config.default.js中的配置。在发布时,测试环境中定义的config会覆盖default中的定义,同样,生产环境中也会覆test中的定义。

本地开发 config.default.js 测试环境 config.test.js -> config.default.js 生产环境 config.prod.js -> config.test.js -> config.default.js

例如应用需要msyql,redis等服务配置。一般而言,在本地和测试环境不太考虑性能问题,所以可以将日志开启为DEBUG模式,开发环境下数据库应为本地数据库。config.default.js如下:

'use strict';
module.exports = appInfo => {
const config = exports = {};
config.app = {
name: 'eggjsapp',
};
config.sequelize = {
dialect: 'mysql',
database: 'eggjsapp',
host: 'localhost',
port: '3306',
username: 'root',
password: '123123',
};
config.redis = {
client: {
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
password: '',
db: 0,
},
};
config.session = {
key: 'eggjsapp', // 承载 Session 的 Cookie 键值对名字
maxAge: 1000 * 60 * 60, // Session 的最大有效时间
httpOnly: true,
};
config.logger = {
// disableConsoleAfterReady: false,
consoleLevel: 'DEBUG',
};
return config;
};

测试环境和生产环境一般而言是数据库上的区别。因此,不再重复书写。与config.test[prod].js 如下

exports.redis = {
client: {
port: 6379, // Redis port
host: '172.1.1.1', // Remote Redis host
password: 'xxx',
db: 0,
},
};
exports.sequelize = {
dialect: 'mysql',
database: 'eggjsapp-test[prod]',
host: 'eggjsapp-test.db.com',
port: '30000',
username: 'root',
password: 'xxxxx',
};

环境变量

可以通过在npm script 中定义需要的环境变量来加载不同的config文件。从而达到不同环境下使用不同的数据库,日志记录等。

日志

对于日志,开发和测试环境下可能不太需要将日志输出到文件,为了方便调试,需要将日志直接打印在控制台上,所以可以通过config.logger的日志等级来达到目的。

Docker

为了达到,一键发布测试与生产环境的效果,并且便于区分生产环境和开发测试环境的配置,这里独立触两个Dockerfile。

Dockerfile

FROM node:8.6.0-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# add npm package
COPY package.json /usr/src/app/package.json
RUN npm i --production
# copy code
COPY . /usr/src/app
EXPOSE 7001
CMD npm start

DEVDockerfile

FROM node:8.6.0-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# add npm package
COPY package.json /usr/src/app/package.json
RUN npm i --production
# copy code
COPY . /usr/src/app
EXPOSE 7001
CMD npm start:test

package.json中npm script定义如下,区分环境变量和端口

{
"start": "egg-scripts start",
"start:test": "EGG_SERVER_ENV=test egg-scripts --port=7008",
}

Gitlab

以上的内容这是包括了代码和镜像,只能在本地使用。想象一下如果需要发布,就需要每次手动docker build并push镜像到远程。

我们想把它的做的更加自动化一点,于是有了持续集成和持续交付的概念。因此,在这里以gitlab为例。通过gitlab到ci能够做到自动生成镜像和push到远程。

定义以下的.gitlab.yml,在gitalb stage中定义docker有关,在CI中生成相应代码生产环境之后,进行docker build,同时push到远程,这里涉及到私有docker登录的问题,所以需要docker login, 为了保证安全,docker密码泄露,可以通过文件的方式或者通过在gitlab中设置私密的环境变量获取 GitLab CI/CD Variables | GitLab

docker:
image: docker:latest
before_script:
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD $DOCKER_REGISTRY
stage: docker
script:
- docker build -t eggjsapp:v1.0.0 .
- docker push eggjsapp:v1.0.0
after_script:
- docker logout $DOCKER_REGISTRY

Kubernetes

如何本地连接远程集群可以参考容器服务 通过 Kubectl 连接集群 - Kubectl 操作集群 - 文档平台 - 腾讯云

需要以下yaml文件

deployment.yaml 文件定义简单的物理环境,以及docker镜像

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
qcloud-app: eggjsapp
name: eggjsapp
namespace: api
spec:
replicas: 1
revisionHistoryLimit: 5
strategy:
type: Recreate
template:
spec:
containers:
- env:
- name: NPM_CONFIG_LOGLEVEL
value: info
- name: NODE_VERSION
value: 8.6.0
- name: YARN_VERSION
value: 1.1.0
image: xxx.com/eggjsapp-v1.51.0
imagePullPolicy: Always
name: shanhushuo
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 250m
memory: 256Mi
imagePullSecrets:
- name: dockerregistrykey
restartPolicy: Always

service.yaml 定义为容器的服务,通过NodePort暴露端口

apiVersion: v1
kind: Service
metadata:
name: eggjsapp-service
namespace: api
spec:
ports:
- name: tcp
nodePort: 30966
port: 80
protocol: TCP
targetPort: 7001

最终需要通过ingress暴露至外网,由于我这里采用的是腾讯的私有云,需要在相应ingress直接指向所需服务即可。参考 容器服务 Ingress转发设置 - 负载均衡 - 文档平台 - 腾讯云

示例:在腾讯云中讲上述所有应用串联起来

  1. 首先需要在腾讯云中授权你的代码,通过gitlab的token授权,授权成功后,可以在镜像配置中构建镜像。可以设置镜像的触发时机。

  2. 建立自己的私有镜像之后就需要来部署了。首先在腾讯云中创建集群,集群创建完成之后创建一个服务

  3. 如果构建的镜像没有问题,服务创建完成之后就可以访问了。可以通过ip+端口进行访问。如果需要进行域名的绑定和端口的映射,可以通过配置ingress来暴露外网。

  4. 如果再做的自动化一点,可以在镜像中设置触发器,当镜像构建完成之后,可以通过触发器使集群中的服务自动更新镜像。

总结

假如获取pod时报如下错误,可以查看下kubectl版本

以下截图为kubectl版本太高后重新安装1.8版本后成功获取pod

进入容器之后查看具体日志文件