CentOS7 安装 Docker 和 Docker-compose

注意需要使用 root 账户或者可以使用 sudo 的账户

1、安装 Docker

# 安装依赖
sudo yum install -y yum-utils 
  device-mapper-persistent-data 
  lvm2

# 添加docker下载仓库
sudo yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo

# 安装docker-ce
sudo yum install docker-ce

# 启动docker-ce
sudo systemctl start docker

# 验证
sudo docker --version

sudo docker run hello-world

2、安装 docker-compose

sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

docker-compose --version

docker部署ELK(logstash、elasticsearch、kibana),监控日志

由于是首次部署,第一次想着是单独部署logstash、elasticsearch、kibana,然后通过配置实现日志的监控,以下为部署步骤,但是最终失败,只能采取docker-compose来部署,以下内容可以略过,仅作为参考。

一、每个单独部署

先部署elasticsearch,因为logstash要设置日志输出位置,而输出位置正是elasticsearch,所以需要先部署启动elasticsearch,logstash才能部署并启动成功。

1、elasticsearch部署

1.制作elasticsearch镜像

docker pull docker.elastic.co/elasticsearch/elasticsearch:6.5.4

2.创建并启动elasticsearch容器

docker run -p 9200:9200 -p 9300:9300 -e “discovery.type=single-node” -d docker.elastic.co/elasticsearch/elasticsearch:6.5.4

通过本地浏览器访问localhost:9200,返回如下内容,则证明elasticsearch部署成功

未分类

官方参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html

2、logstash部署

1.制作logstash镜像

docker pull docker.elastic.co/logstash/logstash:6.5.4

2.创建并启动logstash容器

docker run --rm -it -p 4560:4560 -v /home/xijie/app/mylogstash/pipeline/:/usr/share/logstash/pipeline/ -d docker.elastic.co/logstash/logstash:6.5.4

注意文件pipeline的映射,在pipeline中需要创建logstash的配置文件logstash-springboot.conf,该文件内容如下:

input {
    tcp {
        port => 4560
        codec => json_lines
    }
}
output{
  elasticsearch { hosts => ["localhost:9200"] }
  stdout { codec => rubydebug }
}

注意output中设置了elasticsearch的连接地址:localhost:9200

官方参考文章:https://www.elastic.co/guide/en/logstash/current/docker-config.html

3、kibana部署

1.制作镜像

docker pull docker.elastic.co/kibana/kibana:6.5.4

2.创建并启动容器

docker run -p 5601:5601 --mount type=bind,src=/home/xijie/app/mykibana/kibana.yml,dst=/usr/share/kibana/config/kibana.yml -d mysql/mysql-server:5.7

二、docker-compose一起部署

参考文章:https://www.jianshu.com/p/c2f6e80b2756

1、第一步在docker上安装ELK

1.创建目录

mkdir /home/xijie/app/myelk

2.从github上拉取部署elk所需资料

$ git clone https://github.com/deviantony/docker-elk.git

下载完毕的资料目录如下:

未分类

3.进入刚下载的文件夹内

$ cd docker-elk

4.通过docker-compose创建并启动容器

$ docker-compose up -d

5.这个时候通过docker ps可以看到logstash、elasticsearch、kibana容器已经创建并且启动。

未分类

可以看该elk容器的默认端口为:

  • 5000: Logstash TCP input.
  • 9200: Elasticsearch HTTP
  • 9300: Elasticsearch TCP transport
  • 5601: Kibana

Kibana的web入口:http://localhost:5601

6.接下来进行参数配置,来实现springboot通过ELK查看日志信息。

修改logstash的配置,

进入logstash配置文件所在目录:

cd /home/xijie/app/myelk/dokcer-elk/logstash/pipeline

打开配置文件:

vim logstash.conf

修改内容如下:

input{
        tcp {
                mode => "server"
                port => 5000
                codec => json_lines
                tags => ["data-http"]
        }
}
filter{
    json{
        source => "message"
        remove_field => ["message"]
    }
}
output{
    if "data-http" in [tags]{
        elasticsearch{
                hosts=> ["elasticsearch:9200"]
                index => "data-http-%{+YYYY.MM.dd}"
                }
        stdout{codec => rubydebug}
    }
}

注:

input标签为logstash进数据接口,filter标签为数据过滤器,output为数据出去接口。

input标签使用的是tcp,说明springboot客户端需要将日志传递到该接口,该接口正是logstash服务器接口。

filter将message字段去掉,只是为了当展示springboot的http请求接口的数据更加规整,而不是全部展示在message字段中。

output标签将数据传递给了elasticsearch,这里使用了if,当判断所出数据为所指定tag,才进行下面的配置。特别要注意index的配置,该值在kibana中需要使用,这里指定的index值为:data-http,要注意该值与tag是没有关系的,要注意区分。

未分类

参考文章:https://www.elastic.co/guide/en/logstash/current/index.html

这个时候重启elk即可。

进入docker-elk目录

/home/xijie/app/myelk/docker-elk

然后重启

docker-compose restart

关于elasticsearch、logstash、kibana的配置都在对应目录下的config文件夹中的.yml文件中,只需要修改该文件即可。

2、springboot日志系统配置logstash

1.pom中配置logstash

<dependency>
        <groupId>net.logstash.logback</groupId>
         <artifactId>logstash-logback-encoder</artifactId>
         <version>5.2</version>
</dependency>

2.application.properties文件中配置如下:

#logstash日志收集地址,即logstash服务器地址
logstash.ip_port=172.168.0.165:5000
#日志保存级别
logging.all.level=info
#日志保存地址,该值与logstash没关系,是当日志存在本地File文件内的文件夹地址
logging.levelfile=/home/logs/data-center-service

3.logback.xml文件内容如下,该文件在resources目录

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <property resource="application.properties"></property>
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="${logging.levelfile}" />
    <property name="LOG_LEVEL" value="${logging.all.level}" />
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg  %n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/data-center-service.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
    </appender>
    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>${logstash.ip_port}</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
        <queueSize>1048576</queueSize>
        <keepAliveDuration>5 minutes</keepAliveDuration>
        <!--<customFields>{"application-name":"data-repo-interface"}</customFields>-->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator> <!– 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator –>
                <expression>return message.contains("billing");</expression>
            </evaluator>
            <OnMatch>ACCEPT</OnMatch>
            <OnMismatch>DENY</OnMismatch>
        </filter>-->
    </appender>

    <logger name="elk_logger" level="INFO" additivity="false">
        <appender-ref ref="logstash"/>
    </logger>

    <!--<logger name="com.zaxxer" level="${LOG_LEVEL}"/>-->
    <!--<logger name="org.apache.ibatis" level="${LOG_LEVEL}"/>-->
    <!--<logger name="org.mybatis.spring" level="${LOG_LEVEL}"/>-->
    <!--<logger name="org.springframework" level="${LOG_LEVEL}"/>-->
    <!--<logger name="java.sql.Connection" level="${LOG_LEVEL}"/>-->
    <!--<logger name="java.sql.Statement" level="${LOG_LEVEL}"/>-->
    <!--<logger name="java.sql.PreparedStatement" level="${LOG_LEVEL}"/>-->

    <!-- 日志输出级别 -->
    <root level="${LOG_LEVEL}">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <!--<appender-ref ref="logstash" />-->
    </root>
</configuration>

在上述配置文件中

<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>${logstash.ip_port}</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
        <queueSize>1048576</queueSize>
        <keepAliveDuration>5 minutes</keepAliveDuration>
        <!--<customFields>{"application-name":"data-repo-interface"}</customFields>-->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator> <!– 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator –>
                <expression>return message.contains("billing");</expression>
            </evaluator>
            <OnMatch>ACCEPT</OnMatch>
            <OnMismatch>DENY</OnMismatch>
        </filter>-->
    </appender>

    <logger name="elk_logger" level="INFO" additivity="false">
        <appender-ref ref="logstash"/>
    </logger>

是logstash的主要配置。注意该配置中name=”elk_logger”,这样的指定,即

Logger elkLogger = LoggerFactory.getLogger("elk_logger");

当使用slf4j生成Logger时,只要指定Tag为“elk_logger”的日志输出或打印,都会上传到logstash服务器,并存储到elasticsearch,用kibana查看。

3、配置kibana,现在只要服务器通过指定的Tag打印日志,日志信息将会上传logstash解析,并且存储到elasticsearch,然后只需要kibana配置对应的elasticsearch的index即可看到所需的日志信息。

通过浏览器访问kibana,http://localhost:5601

由于本人是通过虚拟机部署的服务,而且虚拟机的ip为172.168.0.165,所以在宿主机中通过访问http://172.168.0.165:5601即可。

未分类

然后点击左侧的management模块。

未分类

接下来点击Index Patterns,创建index pattern

未分类

接下来填入信息,在index pattern框中填入上述创建ElK时logstash配置文件中的index信息“data-http-*”

未分类

填写的同时,下面会显示出elasticsearch中已经有该index得数据,(注意:在配置kibana时,应该先运行服务器,让服务区打印出对应的index日志,elasticsearch中也保存了该日志,这时才能配置kibana成功)。

然后点击右侧的next step。

未分类

在Time Filter field name列表中选择@timestamp,然后按下Create index pattern按钮及创建成功。

这时点击左侧的Discover模块,选中data-http-*标签即可。在右侧就显示出了日志信息。如下图:

未分类

这样整个ELK与日志系统就搭建完毕了。

4、接下讲一下我们工程中关于http接口日志的配置

由于我们提供的服务器接口是上游,是给其他部门服务的,这里会牵扯到大量的专业数据,而为了避免数据问题的纠纷与接口错误问题的排查,所以需要将特定的接口请求数据保存,这样可以通过resquest与response来排查到底是哪个部门的问题。

1.使用了AOP对每次请求进行日志拦截。

定义SysLogAspect类,该类内容如下:

package com;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.SpringContextUtil;
import com.LoggerEntity;
import com.MailService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
@Slf4j
public class SysLogAspect {

    Logger elkLogger = LoggerFactory.getLogger("elk_logger");


    /**
     * 开始时间
     */
    private long startTime = 0L;
    /**
     * 结束时间
     */
    private long endTime = 0L;

//  @Autowired
//  private ILogService logService;

    public static Map<String, Object> getKeyAndValue(Object obj) {
        Map<String, Object> map = new HashMap<>();
        // 得到类对象
        Class userCla = (Class) obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            f.setAccessible(true); // 设置些属性是可以访问的
            Object val = new Object();
            try {
                val = f.get(obj);
                // 得到此属性的值
                map.put(f.getName(), val);// 设置键值
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        return map;
    }

    //通过该注解来判断该接口请求是否进行cut
    @Pointcut("@annotation(com.hongdaoai.datastore.common.annotation.SysLog)")
    public void cutController() {
    }

    @Before("cutController()")
    public void doBeforeInServiceLayer(JoinPoint joinPoint) {
        log.debug("doBeforeInServiceLayer");
        startTime = System.currentTimeMillis();
    }

    @After("cutController()")
    public void doAfterInServiceLayer(JoinPoint joinPoint) {
        log.debug("doAfterInServiceLayer");
    }

    @Around("cutController()")
    public Object recordSysLog(ProceedingJoinPoint joinPoint) throws Throwable {

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();

        ServletRequestAttributes sra = (ServletRequestAttributes) ra;

        HttpServletRequest request = sra.getRequest();

        HttpServletResponse response = sra.getResponse();

        //ELK日志实体类
        LoggerEntity elkLog = new LoggerEntity();

        //应用程序名称
        elkLog.setApplicationName(SpringContextUtil.getApplicationName());

        //profile.active
        elkLog.setProfileActive(SpringContextUtil.getActiveProfile());

        // 请求的类名
        elkLog.setClassName(joinPoint.getTarget().getClass().getName());

        // 请求的方法名
        elkLog.setMethodName(joinPoint.getSignature().getName());

        //请求完整地址
        elkLog.setUrl(request.getRequestURL().toString());

        //请求URI
        elkLog.setUri(request.getRequestURI());

        //请求类型
        elkLog.setRequestMethod(request.getMethod());

        String queryString = request.getQueryString();

        Object[] args = joinPoint.getArgs();

        String params = "";

        //获取请求参数集合并进行遍历拼接
        if (args.length > 0) {

            if ("POST".equals(request.getMethod())) {

                //param
                Object object = args[0];

                Map<String, Object> map = new HashMap<>();

                Map paramMap = getKeyAndValue(object);

                map.put("param", paramMap);

                if (args.length > 1) {

                    object = args[1];

                    Map bodyMap = getKeyAndValue(object);

                    map.put("body", bodyMap);
                }

                params = JSON.toJSONStringWithDateFormat(map, "yyyy-MM-dd HH:mm:ss", SerializerFeature.UseSingleQuotes);

            } else if ("GET".equals(request.getMethod())) {

                params = queryString;

            }
        }

        //请求参数内容(param)
        elkLog.setRequestParamData(params);

        //客户端IP
        elkLog.setClientIp(request.getRemoteAddr());

        //终端请求方式,普通请求,ajax请求
        elkLog.setRequestType(request.getHeader("X-Requested-With"));

        //sessionId
        elkLog.setSessionId(request.getRequestedSessionId());

        //请求时间
//        elkLog.setRequestDateTime(new Date(startTime));
        elkLog.setRequestDateTime(new Date());

        Object result = null;

        try {

            // 环绕通知 ProceedingJoinPoint执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。
            result = joinPoint.proceed();

        } catch (Exception e) {

            endTime = System.currentTimeMillis();

            //请求耗时(单位:毫秒)
            elkLog.setSpentTime(endTime - startTime);

            //接口返回时间
            elkLog.setResponseDateTime(new Date(endTime));

            //请求时httpStatusCode代码
            elkLog.setHttpStatusCode(String.valueOf(response.getStatus()));

            String elkLogData = JSON.toJSONStringWithDateFormat(elkLog, "yyyy-MM-dd HH:mm:ss.SSS");

            elkLogger.error(elkLogData);

//            log.error(e.getMessage(), e);

            throw e;

//            return result;

        }

        endTime = System.currentTimeMillis();

        //请求耗时(单位:毫秒)
        elkLog.setSpentTime(endTime - startTime);

        if (!elkLog.getMethodName().equals("getBookOriginalContent")) {

            //接口返回数据
            elkLog.setResponseData(JSON.toJSONStringWithDateFormat(result, "yyyy-MM-dd HH:mm:ss", SerializerFeature.UseSingleQuotes));

        }

        //接口返回时间
        elkLog.setResponseDateTime(new Date(endTime));

        //请求时httpStatusCode代码
        elkLog.setHttpStatusCode(String.valueOf(response.getStatus()));

//        if (SpringContextUtil.getActiveProfile().equals("prod")) {
        String s = JSON.toJSONStringWithDateFormat(elkLog, "yyyy-MM-dd HH:mm:ss.SSS");
        elkLogger.info(s);

        return result;
    }


}

SpringContextUtil类内容如下:

package com;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext context = null;

    // 传入线程中
    public static <T> T getBean(String beanName) {
        return (T) context.getBean(beanName);
    }

    // 国际化使用
    public static String getMessage(String key) {
        return context.getMessage(key, null, Locale.getDefault());
    }

    /// 获取应用程序名称
    public static String getApplicationName() {
        return context.getEnvironment().getProperty("spring.application.name");
    }

    /// 获取当前环境
    public static String getActiveProfile() {
        return context.getEnvironment().getActiveProfiles()[0];
    }

    /* (non Javadoc)
     * @Title: setApplicationContext
     * @Description: spring获取bean工具类
     * @param applicationContext
     * @throws BeansException
     * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
    }
}

LoggerEntity类内容如下:

package com;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

//@Entity
//@Table(name = "t_logger_infos")
@Data
public class LoggerEntity implements Serializable {

    //应用程序名
    private String applicationName;

    //spring.profiles.active
    private String profileActive;

    //客户端请求ip
    private String clientIp;

    //客户端请求路径
    private String uri;

    //客户端请求完整路径
    private String url;

    //请求方法名
    private String methodName;

    //请求类名
    private String className;

    //终端请求方式,普通请求,ajax请求
    private String requestType;

    //请求方式method,post,get等
    private String requestMethod;

    //请求参数内容,json
    private String requestParamData;

    //请求body参数内容,json
    private String requestBodyData;

    //请求接口唯一session标识
    private String sessionId;

    //请求时间
    private Date requestDateTime;

    //接口返回时间
    private Date responseDateTime;

    //接口返回数据json
    private String responseData;

    //请求时httpStatusCode代码,如:200,400,404等
    private String httpStatusCode;

    //请求耗时秒单位
    private long spentTime;

}

SysLog注解类如下:

package com;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

    boolean isLog() default true;

}

在SysLogAspect切面类中使用@SysLog注解来判断该接口是否进行日志传递。

所以在我们写的接口中只要在方法上加入该注解,即可控制日志是否上传。

如该Controller中所定义接口:

@SysLog(isLog = true)
@RequestMapping(value = "/getDataList", method = RequestMethod.POST)
public ApiResult getInputDataList(@RequestBody VersionVo vo) {
    return success();
}

个推基于 Docker 和 Kubernetes 的微服务实践

一、微服务化

微服务架构

未分类

微服务 是将单一的应用程序拆分成多个微小的服务,各个小服务之间松耦合,高内聚,每个小的服务可以单独进行开发,不依赖于具体的编程语言,也可以使用不同的数据存储技术,各个服务可以独立部署,拥有各自的进程,相互之间通过轻量化的机制进行通信(如基于HTTP的API接口),所有的服务共同实现具体的业务功能。

未分类

客户端与服务端通信有2种方式,第一种是客户端直接与各个微服务进行通信,这样的架构有4个缺点:

(1)多次服务请求,效率低;

(2)对外暴露服务接口;

(3)接口协议无法统一;

(4)客户端代码复杂,服务端升级困难。

第二种方式是由API网关统一代理各个服务,对外提供统一的接口协议,该架构有3 个优势:

(1)封装服务接口细节,减少通信次数;

(2)统一通信协议,减少客户端代码耦合;

(3)统一鉴权,流控,防攻击;

在该架构下,网关也有可能成为系统瓶颈。

未分类

相应地,这2种架构也带来了2种服务注册发现的方式,第一种是客户端通过向服务的注册中心查询微服务的地址与其通信,第二种是增加统一的API网关来查询。前者会增加客户端的复杂度,开发成本高,第二种操作会显得更加简洁,因此我们在实践的时候选择了第二种架构方式。

微服务数量增加以后,服务之间的调用关系易产生耦合,甚至出现循环调用的情况,最好的应对方法是对服务进行分层,即将相互依赖的服务通过消息队列等技术进行异步解耦,减少服务间的依赖。

未分类

-服务分层-

微服务的具体实践

1. 技术选型

在实践中,我们的API Gateway使用的是OpenResty, OpenResty基于Nginx并扩展了对Lua的支持,可构建高并发的Web服务。我们通过HTTP接口实现客户端通信,数据基本封装成JSON格式,服务间的通信接口也是基于HTTP,并利用消息队列进行异步解耦;至于服务注册发现,我们使用的是Consul;我们选择了Lua(扩展API Gateway的功能),Node.js(用于开发后端服务),Java(用于密集计算和与大数据通信的场景)作为主要的开发语言。

2. 具体实现过程

未分类

在实践过程中,我们使用Lua开发了自己的微服务框架——WebLua,其封装服务之间的通信协议和访问外部资源(如Mysql、Redis等)的方法和依赖,同时提供了应用插槽。我们可以将每一个APP看成一个功能模块,每个APP都需要插到WebLua中才能运行。WebLua可以方便地将模块进行组合,既可以一个APP运行一个微服务,也可以多个APP一起对外提供服务。如此,开发者只需关注业务APP开发,很大程度上提高了开发效率。上图右侧是具体的代码目录结构,每个APP可分为Action,Page,Data三层,Action层在请求处理前后进行拦截,可做某些特殊处理,如请求前进行权限校验等;Page层主要对请求的参数进行解析和校验;Data层负责具体业务处理,同时提供了Shell脚本,可实现APP打包和部署安装。

二、 API网关

在架构中一个重要角色就是API网关,下面来做一个介绍。

未分类

从上面的对比图中可以看到,左侧是没有API Gateway的,很多的模块如Auth,Logging等,这些代码都需要自己去实现,造成了模块的重复建设,同时侵入了服务,功能扩展比较困难;右侧的图是使用了API Gateway之后的架构图,所有通用模块均在API Gateway实现,维护简单,一处建设,各处受益。在这种情况下,对API Gateway也提出了更高要求——其功能必须可以很方便地扩展。

为了实现这样的API网关,我们基于 OpenResty,借鉴了Kong和Orange的插件机制,通过插件来扩展API网关功能。

未分类

从上面的API Gateway架构图中可以看到,网关安装诸多插件,每个插件会在请求的一个或多个阶段发挥作用。插件配置会在Consul上更新,实时生效,插件规则可灵活配置。在操作中,我们为插件开发者提供了更多的自由,开发者可以自己定义格式。

三、容器化

在微服务落地实践时我们选择了Docker,下面将详细介绍个推基于Docker的实践。

首先网络组件选择的是Calico,服务注册发现和配置管理选择的是Consul。Consul-Template可实时监测Consul配置和服务的变化。

未分类

个推镜像体系是以CentOS为基础系统镜像,安装OpenResty,Nodejs,JDK,由此得到环境镜像,再在这个基础上安装微服务框架,获得Gorp镜像。再在这个基础上安装具体应用服务,得到应用服务镜像。

未分类

-服务注册发现和配置更新流程-

在API网关中,服务注册通过Consul-Agent来实现,配置更新通过Consul-Template实现。Consul-Template主要更新3类配置,包括:Services:代理的所有微服务的服务地址;Products:简言之即请求到微服务的映射表,如左上所示,所有请求都有统一个规范,从Host中可以获取Prod,从URI中可以获取APP,这 2个信息可将请求动态路由到具体服务;Nging-Conf:产品的Nginx配置。

未分类

应用服务容器,服务注册的方式跟API网关一致。首先,服务通过容器内部运行的Consul Agent将服务注册到Consul上,其次通过Consul-Template来监测观察 Consul上配置的变化,并更新配置文件。OpenResty或者WebNode配置的更新是直接覆盖相应的配置文件,然后重启对应的服务。

未分类

上图是个推基于Docker的集群架构,从中可看到,Docker集群包括3个节点,整个微服务分为3层,最上层是API Gateway,中间是业务层,最下层是一些多产品公用的基础的微服务。

四、Kubernetes实践

微服务虽然有很多好处,但也带来了很多问题,其中一个就是运维复杂。以前运维只需要面对一个单体应用即可,现在可能面临的是几十甚至上百的微服务。在这种情况下,我们需要借助Kubernetes来解决问题。Kubernetes是Google开源的一个容器编排工具,可用于协助管理容器。

一开始,我们将容器向Kubernetes集群迁移时,没做任何改变,只是采用Pod将所有的服务体系在Kubernetes集群运行。但随着深入使用Kubernetes,我们对微服务做了一些改变。

1.首先我们换成用Deployment的方式来部署服务,Deployment会保证服务时刻有一定的副本存活,提高了服务稳定性。

2.其次,我们使用了Service,它可以代理Pod实现负载的均衡。

  1. Kube-DNS可以将Service名解析成具体的ClusterIP,并且当Service没有删除重建时,其ClusterIP不变,如此DNS解析的缓存就不存在失效问题。基于Kube-DNS和Service的特性,后续我们改造了服务注册发现体系。

未分类

上图是我们当前的服务部署方式,Pod用Deployment的方式创建,用Service来进行代理。

在实践过程中,我们还遇到了另一个问题,即配置管理问题。

(1)微服务化后配置文件多而分散;

(2)不同环境之间有很多不必要的差异,如数据库名;

(3)在很多不同环境中,相同的配置项暴露给测试和运维;

(4)没有版本控制,回滚比较麻烦;

(5)基于Consul的Web UI无法对非法的输入进行校验。

针对这些问题我们做了以下调整:

(1) 统一不同环境间不必要的差异;

(2) 对配置文件进行模板化,只暴露差异部分,同时可实现不同配置文件集中配置;

(3)基于Consul开发配置中心,对产品配置集中管理;对输入进行合法性校验;增加版本控制,方便回滚。

未分类

-配置中心流程图-

未分类

关于日志服务,我们在应用容器中集成了Fluent-Bit,配置了2个输入源,TCP和tail, 输出也有2个,一个是Elasticsearch,所有的日志都会上传到ES通过Kibana展示查询,另一个是日志审计服务,有些需要进行审计的操作日志会发送到日志审计服务进行进一步的分析处理。

微服务数量增加以后,请求链路可能延长,开发者在追踪问题和排查性能瓶颈时会很不方便,因此我们引入了Zipkin,其主要用于分布式链路追踪,在API Gateway实现了一个插件进行Span收集,后端服务则通过开源的中间件来实现。

未分类

上图是个推目前的整体架构图,最底层是K8S集群,上面部署了Kube-DNS,Consul用于服务注册发现和配置管理,再者是我们分层的微服务体系,右侧是一些辅助的管理系统。

五、总结

上述是个推基于Docker和Kubernetes的整个微服务实践过程,我们在实践微服务过程中做了9件重要的事情, 简化了操作流程,提高了工作效率 。个推设计实现了自己的微服务框架,完成微服务的容器化部署,自研API网关,并基于Consul的服务注册和配置管理,使用Kubernetes对容器进行编排,基于Service和Kube-DNS对服务注册和发现体系进行改造,搭建了自己的配置中心,优化了日志服务,实现了Zipkin链路追踪。

k8s(十三)、企业级docker仓库Harbor在kubernetes上搭建使用

前言

Harbor是Docker镜像仓库管理系统,用于企业级管理容器镜像,支持对接ldap进行权限管理,由VMVare中国团队开发。

项目地址:
https://github.com/goharbor/harbor/

安装方式:
1.Docker Compose方式部署:
https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md

2.通过kubernetes直接部署:
新版本已废弃

3.通过helm/kubernetes方式部署:
https://github.com/goharbor/harbor-helm/blob/master/README.md
推荐使用,本文将采用此方式

Helm安装

helm简介

Helm是一个用于Kubernetes的包管理工具,每一个安装包构成一个chart基本单位.想象一下,一个完整的部署,可能包含前端后端中间件DB的部署实例,以及k8s管理对应的secret/configmap/svc/ing/pv/pvc等众多资源类型,在部署管理或者卸载之前,如果逐一编辑管理这些资源的yaml文件,工作量比较庞大且容易疏漏.helm的出现很好的解决了这个问题,使用模板以及变量替换的方式,按需生成部署yaml文件包,并且向kubernetes api发送调度请求,虽然额外地引入了一些学习成本,但对于部署包的管理无疑是带来了很大的便利性的.

helm组件

Helm

Helm是一个cli客户端,可以完成如下内容:

1.创建/打包/调试Chart
2.创建本地Chart仓库,管理本地和远程Chart仓库
3.与Tiller交互并完成Chart的安装,升级,删除,回滚,查看等操作

Tiller

1.监听helm客户端的请求,根据chart生成相应的release
2.将release的资源信息发送给kubernetes api,实现资源增删改查.
3.追踪release的状态,实现资源的更新/回滚操作

安装:
安装较为简单,参考官方文档,不做复述:
https://docs.helm.sh/using_helm/#installing-helm

Harbor安装

这里采用次新版本的v1.5.3版本

root@h1:# wget https://github.com/goharbor/harbor/archive/v1.5.3.tar.gz
root@h1:# tar -xf https://github.com/goharbor/harbor/archive/v1.5.3.tar.gz 
root@h1:# cd harbor-1.5.3/contrib/helm/harbor/

helm方式安装的harbor默认是https的,因此需要k8s集群ingress网关开启https协议,traefik开启https参考此前文章:
https://blog.csdn.net/ywq935/article/details/79886793

更新helm dependency

harbor的helm部署依赖了postgresql的helm,在官方的安装文档没有明确说明,直接按照官方文档说明安装,就会缺失postgresql的部署,导致helm install failed

helm dependency update

修改value.yaml文件,添加pvc持久存储并将相应组件上下文中的volume部分注释打开:

mysql:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce

adminserver:
  volumes:
    config:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 1G

registry:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 5G

完整value.yaml文件如下:

# Configure persisten Volumes per application
## Applications that require storage have a `volumes` definition which will be used
## when `persistence.enabled` is set to true.
## example
# mysql:
#   volumes:
#     data:
## Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
##   set, choosing the default provisioner.  (gp2 on AWS, standard on
##   GKE, AWS & OpenStack)
##
#     storageClass: "-"
#     accessMode: ReadWriteOnce
#     size: 1Gi

mysql:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
adminserver:
  volumes:
    config:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 1G

registry:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 5G

## Configure resource requests and limits per application
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
# mysql:
#   resources:
#     requests:
#       memory: 256Mi
#       cpu: 100m

persistence:
  enabled: true

# The tag for Harbor docker images.
harborImageTag: &harbor_image_tag v1.4.0

# The FQDN for Harbor service.
externalDomain: harbor.my.domain
# If set to true, you don't need to set tlsCrt/tlsKey/caCrt, but must add
# Harbor FQDN as insecure-registries for your docker client.
insecureRegistry: false
#insecureRegistry: true
# The TLS certificate for Harbor. The common name of tlsCrt must match the externalDomain above.
tlsCrt: 
#tlsCrt: root/traefik/ssl/kokoerp.crt
tlsKey:
#tlsKey: root/traefik/ssl/kokoerp.key
caCrt:

# The secret key used for encryption. Must be a string of 16 chars.
secretKey: not-a-secure-key

# These annotations allow the registry to work behind the nginx
# ingress controller.
ingress:
  annotations:
    ingress.kubernetes.io/ssl-redirect: "true"
    ingress.kubernetes.io/body-size: "0"
    ingress.kubernetes.io/proxy-body-size: "0"

adminserver:
  image:
    repository: vmware/harbor-adminserver
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  emailHost: "smtp.mydomain.com"
  emailPort: "25"
  emailUser: "[email protected]"
  emailSsl: "false"
  emailFrom: "admin <[email protected]>"
  emailIdentity: ""
  emailInsecure: "False"
  emailPwd: not-a-secure-password
  adminPassword: Harbor12345
  authenticationMode: "db_auth"
  selfRegistration: "on"
  ldap:
    url: "ldaps://ldapserver"
    searchDN: ""
    baseDN: ""
    filter: "(objectClass=person)"
    uid: "uid"
    scope: "2"
    timeout: "5"
    verifyCert: "True"
  ## Persist data to a persistent volume
  volumes:
    config:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

## jobservice
#
jobservice:
  image:
    repository: vmware/harbor-jobservice
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  secret: not-a-secure-secret
# resources:
#   requests:
#     memory: 256Mi
#     cpu: 100m

## UI
#
ui:
  image:
    repository: vmware/harbor-ui
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  secret: not-a-secure-secret
  privateKeyPem: |
    -----BEGIN RSA PRIVATE KEY-----
    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
    -----END RSA PRIVATE KEY-----
# resources:
#  requests:
#    memory: 256Mi
#    cpu: 100m

## MySQL Settings. Currently Harbor does not support an external
## MySQL server, only their own image. Until this is fixed, do not
## Change the settings below.
#
mysql:
  image:
    repository: vmware/harbor-db
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  # If left blank will use the included mysql service name.
  host: ~
  port: 3306
  user: "root"
  pass: "registry"
  database: "registry"
  volumes:
    data:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

registry:
  image:
    repository: vmware/registry-photon
    tag: v2.6.2-v1.4.0
    pullPolicy: IfNotPresent
  httpSecret: not-a-secure-secret
  logLevel:
# comment out one of the below to use your cloud's object storage.
#  objectStorage:
#    gcs:
#      keyfile: ""
#      bucket: ""
#      chunksize: "5242880"
#    s3:
#      region: ""
#      accesskey: ""
#      secretkey: ""
#      bucket: ""
#      encrypt: "true"
#    azure:
#      accountname: ""
#      accountkey: ""
#      container: ""
  rootCrt: |
    -----BEGIN CERTIFICATE-----
    MIIE0zCCArugAwIBAgIJAIgs3S+hsjhmMA0GCSqGSIb3DQEBCwUAMAAwHhcNMTcx
    MTA5MTcyNzQ5WhcNMjcxMTA3MTcyNzQ5WjAAMIICIjANBgkqhkiG9w0BAQEFAAOC
    Ag8AMIICCgKCAgEA4WYbxdrFGG6RnfyYKlHYML3lEqtA9cYWWOynE9BeaEr/cMnM
    bBr1dd91/Nm6RiYhQvTDU2Kc6NejqjdliW5B9xUoVKayri8OU81a8ViXeNgKwCPR
    AiTTla1zoX5DnvoxpO9G3lxyNvTKXc0cw8NjQDAXpaDbzJYLkshCeuyD9bco8R96
    /zrpBEX8tADN3+3yA3fMcZzVXsBm4BTpHJRk/qBpHYEPSHzxyH3iGMNKk3vMUBZz
    e0EYkK8NCA2CuEKMnC3acx9IdRwkx10abGvHQCLRCVY7rGoak+b0oZ99RJIRQ9Iq
    YXsn8fsMBQly6xxvSeY5XuSP7Xb6JKDt3y8Spi4gR1M/5aEzhuOyu201rMna7Rs/
    GPfaKjBlbX0jiLDa7v4zjsBPsPaf/c4uooz3ICLsdukaom+E538R0EiOkXt/wyw2
    2YmaWNCsYlEpke7cVC33e/0dPBq4IHsVflawSF9OWS23ikVAs/n+76KjuucEDmbT
    aKUYAJjvAmZL14j+EKc/CoplhCe6pKhavjmNIOfCSdlreIPBhOVbf1f817wKoSIZ
    qVyCA1AYNkI9RYS00axtJGBGMlKbdQqCNpLL58c6To2awmckIZCEcATKOp++NoGm
    Ib0bhdSasdGB5VCtwZVluN8bLl13zBKoxTGjNlEatUGDRnDAnLdZbXXffjsCAwEA
    AaNQME4wHQYDVR0OBBYEFCMYYMOL0E/Uyj5wseDfIl7o4ELsMB8GA1UdIwQYMBaA
    FCMYYMOL0E/Uyj5wseDfIl7o4ELsMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
    BQADggIBABG8fPvrrR+erpwQFuB/56j2i6sO+qoOJPpAMYwkzICrT0eerWAavwoy
    f0UAKN7cUeEJXjIR7s7CogGFijWdaWaQsXUD0zJq5aotLYZLimEc1O0uAmJEsfYC
    v7mG07eU6ge22sSo5hxhVplGt52hnXnT0DdgSRZpq2mvgd9lcopAidM+KHlaasXk
    IecHKM99KX9D8smr0AcQ6M/Ygbf2qjO9YRmpBIjyQWEake4y/4LWm+3+v08ecg4B
    g+iMC0Rw1QcPqgwaGaWu71RtYhyTg7SnAknb5nBcHIbLb0hdLgQTa3ZdtXgqchIi
    GuFlEBmHFZP6bLJORRUQ0ari5wpXIsYfrB4T8PybTzva3OCMlEsMjuysFr9ewhzM
    9UGLiSQNDyKA10J8WwlzbeD0AAW944hW4Dbg6SWv4gAo51T+6AukRdup5y6lfQ5a
    h4Lbo6pzaA369IsJBntvKvia6hUf/SghnbG7pCHX/AEilcgTb13HndF/G+7aZgKR
    mi9qvNRSDsE/BrgZawovp81+j6aL4y6UtXYspHr+SuWsKYsaH7pl5HspNCyJ5vV6
    dpJAwosFBqSEnI333wAunpMYmi/jKHH/j4WqjLnCInp0/wouzYu42l8Pmz591BSp
    Jag500bEBxqI2RLELgMt/bUdjp4N2M7mrxdrN+2579HTzb6Hviu9
    -----END CERTIFICATE-----
  ## Persist data to a persistent volume
  volumes:
    data:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 5Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

clair:
  enabled: true
  image:
    repository: vmware/clair-photon
    tag: v2.0.1-v1.4.0
    pullPolicy: IfNotPresent
## The following needs to match the credentials
## in the `postgresql` configuration under the
## `postgresql` namespace below.
  postgresPassword: not-a-secure-password
  postgresUser: clair
  postgresDatabase: clair
# resources:
#  requests:
#    memory: 256Mi
#    cpu: 100m
# pgResources:
#  requests:
#    memory: 256Mi
#    cpu: 100m
#  volumes:
#    pgData:
#      storageClass: "cephrbd"
#      accessMode: ReadWriteOnce
#      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

## Notary support is not yet fully implemented in the Helm Charts
## Enabling it will just break things.
#
notary:
  enabled: false

## Settings for postgresql dependency.
## see https://github.com/kubernetes/charts/tree/master/stable/postgresql
## for further configurables.
postgresql:
  postgresUser: clair
  postgresPassword: not-a-secure-password
  postgresDatabase: clair
  persistence:
    # enabled: false
    enabled: true
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 8Gi

部署

helm install . --debug --name hub --set externalDomain=hub.test.com

等待一段时间的镜像拉取后,查看pod:

hub-harbor-adminserver-0                 1/1       Running            1          3h
hub-harbor-clair-6c7d9dcdb7-q4lv4        1/1       Running            2          3h
hub-harbor-jobservice-75f7fbcc9c-ggwp4   1/1       Running            2          3h
hub-harbor-mysql-0                       1/1       Running            0          3h
hub-harbor-registry-0                    1/1       Running            1          3h
hub-harbor-ui-57b4674ff9-kcfq6           1/1       Running            0          3h
hub-postgresql-ccf8d56d5-jg4wq           1/1       Running            1          3h

添加dns,指向ingress gateway(即traefik的node ip):

echo "192.168.1.238  hub.test.com"  >> /etc/hosts

浏览器打开测试,默认登录口令为admin/Harbor12345

未分类

可以看到默认有一个公开项目library,尝试往这里推送镜像

Docker推送测试

找一台安装了docker的机器,修改docker服务脚本:

~# cat /lib/systemd/system/docker.service

#ExecStart=/usr/bin/dockerd -H fd://
ExecStart=/usr/bin/dockerd  --insecure-registry hub.kokoerp.com

重启docker并登录测试:

~# systemctl daemon-reload
~# systemctl restart docker

root@h2:~# docker login hub.test.com
Username (admin): admin
Password: 
Login Succeeded

推送镜像测试:

root@h2:~# docker tag busybox hub.test.com/library/busybox:test1
root@h2:~# docker push hub.test.com/library/busybox:test1
The push refers to a repository [hub.test.com/library/busybox]
8a788232037e: Pushed 
test1: digest: sha256:915f390a8912e16d4beb8689720a17348f3f6d1a7b659697df850ab625ea29d5 size: 527

可以看到推送成功

ldap配置

未分类

harbor可以基于企业内部的ldap来获取人员信息,可以在ui上创建非公开项目,将ldap中获取的人员加入项目并赋予相应pull push权限,ui操作界面比较友好,这里就不展示了.

基于k8s、docker、jenkins构建springboot服务

Jenkins + github + docker + k8s + springboot

本文介绍基于k8s、docker、jenkins、springboot构建docker服务。

环境准备

server-1 k8s-master Centos7 ip地址10.12.5.110server-2 k8s-node Centos7 ip地址10.12.5.115

两台服务执行如下命令

$ setenforce 0$ systemctl stop firewalld$ systemctl disable firewalld

server-1 k8s-master 安装

k8s-master节点安装kubernets、docker、etcd、git、maven等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerOPTIONS='--registry-mirror=https://docker.mirrors.ustc.edu.cn --selinux-enabled --log-driver=journald --signature-verification=false'ADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ service docker start

安装registry

$ docker pull registry$ mkdir -p /data/docker/registry$ docker run -d -p 5000:5000 -v /data/docker/registry:/var/lib/registry registry

安装etcd

$ yum install etcd$ vi /etc/etcd/etcd.conf// 修改如下配置ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"$ systemctl enable etcd$ systemctl start etcd

安装kubernetes

$ yum install kubernetes// 修改配置文件apiserver$ vi /etc/kubernetes/apiserver// 修改如下配置KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"KUBE_API_PORT="--port=8080"KUBELET_PORT="--kubelet-port=10250"KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ResourceQuota"// 启动服务$ systemctl enable kube-apiserver kube-controller-manager kube-scheduler$ systemctl start kube-apiserver kube-controller-manager kube-scheduler

安装git和mvn

$ yum install git$ yum install maven

server-2 k8s-node 安装
k8s-master节点安装kubernets、docker等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ systemctl start docker

安装kubernetes

$ yum install kubernetes// 修改配置文件kubelet$ vi /etc/kubernetes/kubelet// 修改如下配置KUBELET_ADDRESS="--address=0.0.0.0"KUBELET_PORT="--port=10250"KUBELET_HOSTNAME="--hostname-override=10.12.5.115"KUBELET_API_SERVER="--api-servers=http://10.12.5.110:8080"// 修改配置文件config$ vi /etc/kubernetes/config// 修改如下配置KUBE_MASTER="--master=http://10.12.5.110:8080"// 启动服务$ systemctl enable kube-proxy kubelet$ systemctl start kube-proxy kubelet

检查k8s集群运行情况

// 在k8s-master节点执行$ kubectl get nodesNAME          STATUS    AGE10.12.5.115   Ready     3m

在k8s-master节点builder springcloudenv镜像

* 下载jdk8$ mdkir -p /home/docker/docker-jdk8$ wget http://javadl.oracle.com/webapps/download/AutoDL?BundleId=233162_512cd62ec5174c3487ac17c61aaa89e8 -O jre-8u161-linux-x64.tar.gz$ tar zxvf jre-8u161-linux-x64.tar.gz* 删除jdk中用不到的文件,尽量减少docker镜像文件大小$ cd jre1.8.0_171$ rm -rf lib/plugin.jar lib/ext/jfxrt.jar bin/javaws lib/javaws.jar lib/desktop plugin lib/deploy* lib/*javafx* lib/*jfx* lib/amd64/libdecora_sse.so lib/amd64/libprism_*.so lib/amd64/libfxplugins.so lib/amd64/libglass.so lib/amd64/libgstreamer-lite.so lib/amd64/libjavafx*.so lib/amd64/libjfx*.so* 编写Dockerfile文件$ cd /home/docker/docker-jdk8$ vi DockerfileFROM centosMAINTAINER by wangtwRUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&     echo 'Asia/Shanghai' >/etc/timezone &&     yum -y install kde-l10n-Chinese &&     localedef -c -f UTF-8 -i zh_CN zh_CN.utf8 &&     mkdir -p /usr/javaCOPY jre1.8.0_171 /usr/java/jre1.8.0_171ENV LANG zh_CN.UTF-8ENV JAVA_HOME /usr/java/jre1.8.0_171ENV PATH $JAVA_HOME/bin:$PATH* 创建镜像springcloudenv,并上传到registry,确保docker registry已经启动$ docker build -t springcloudenv .$ docker tag springcloudenv springcloudenv:v1$ docker push springcloudenv:v1// 查看registry中的镜像$ curl http://10.12.5.110:5000/v2/_catalog{"repositories":["springcloudenv"]}

安装nfs

在k8s-master节点安装nfs server

$ yum install -y nfs-utils rpcbind$ mkdir -p /data/mysql-pv$ chmod 777 /data/mysql-pv/$ mkdir -p /data/nfs$ chmod 666 /data/nfs/$ vi /etc/exports// 增加如下行/data/nfs 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)/data/mysql-pv 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)$ systemctl enable rpcbind nfs$ systemctl start rpcbind nfs$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

在k8s-node节点安装nfs

$ yum install -y nfs-utils$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

安装mysql docker

以持久化方式运行myql

在k8s-master节点创建相关文件

$ mkdir -p /home/k8s/yml/services/mysql$ cd /home/k8s/yml/services/mysql$ vi mysql-pv.yamlapiVersion: v1kind: PersistentVolumemetadata:    name: mysql-pvspec:    accessModes:    - ReadWriteOnce    capacity:    storage: 1Gi    persistentVolumeReclaimPolicy: Retain#storageClassName: nfs    nfs:    path: /data/mysql-pv    server: 10.12.5.110$ vi mysql-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:    name: mysql-pvcspec:  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 1Gi  #storageClassName: nfs$ vi mysql.yamlapiVersion: v1kind: Servicemetadata:  name: mysqlspec:  ports:  - port: 3306  selector:    app: mysql---apiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: mysqlspec:  replicas: 1  selector:    matchLabels:      app: mysql  template:    metadata:      labels:        app: mysql    spec:      containers:      - name: mysql        image: mysql:5.6        env:        - name: MYSQL_ROOT_PASSWORD          value: password        ports:        - containerPort: 3306          name: mysql        volumeMounts:         - name: mysql-persistent-storage          mountPath: /var/lib/mysql      volumes:      - name: mysql-persistent-storage        persistentVolumeClaim:          claimName: mysql-pvc

创建mysql service和pod

$ cd /home/k8s/yml/services/mysql$ kubectl create -f mysq-pv.yaml$ kubectl get pvNAME       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM               REASON    AGEmysql-pv   1Gi        RWO           Retain          Bound     default/mysql-pvc             2d$ kubectl create -f mysq-pvc.yaml$ kubectl get pvcNAME        STATUS    VOLUME     CAPACITY   ACCESSMODES   AGEmysql-pvc   Bound     mysql-pv   1Gi        RWO           2d$ kubectl create -f mysq.yaml$ kubectl get serviceNAME         CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGEmysql        10.254.151.113   <none>        3306/TCP   2d$ kubectl get podNAME                           READY     STATUS             RESTARTS   AGEmysql-3827607452-hd5ct         1/1       Running            80         19h

k8s-master节点安装和配置jenkins

$ mkdir /home/jenkins$ cd$ vi .bash_profileexport JENKINS_HOME=/home/jenkins$ cd /home/jenkins$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war$ java -jar jenkins.war --httpPort=9090&浏览器打开地址:http://10.12.5.110:9090把文件/home/jenkins/secrets/initialAdminPassword的内容复制到安装页面解决插件安装离线问题打开地址:http://10.12.5.110:9090/pluginManager/advanced把https://updates.jenkins.io/update-center.json修改为http://updates.jenkins.io/update-center.json安装插件:Publish Over SSH

创建jenkins任务,源代码在github上

* 任务名称:springboottest* 任务类型:自由风格* 创建github访问CredentialCredential类型:username with password* 源码管理 选择gitRepository URL 值为https://github.com/wangtiewu/springboottest.gitCredentials 值为上一步创建的 Credential* 创建构建脚本mvn clean packagetag=$( date +'%Y%m%d%H%M' )app=springcloudtestecho $tag > version_$app.txtdocker_name=$appdocker build -t $docker_name:$tag .docker push $docker_name:$tag* 创建ssh server系统管理-〉系统设置菜单-〉publish over ssh* 增加构建后操作,Send build artifacts over SSHSSH Server:name 值为k8s masterTransfer Set:Source files 值为version_springcloudtest.txtTransfer Set:Remote directory   值为springcloudtestTransfer Set:Exec command 值为cd /home/k8s/yml/services/springcloudtestapp=springcloudtestdeploy_file=$app.yamlversion_file=version_$app.txtpatch_file=$app.patchnew_version=$( cat $version_file )old_version=$( cat $deploy_file | grep image | awk -F ":" '{print $3}' )sed -i "s/$old_version/$new_version/" $deploy_filedeployment_num=$( /usr/bin/kubectl get deployment | grep springcloudtest | wc -l )if [ $deployment_num -eq 1 ];then    p_old_version=$( cat $patch_file | jq '.spec.template.spec.containers[0].image' )    p_new_version="$app:$new_version"    sed -i "s/$p_old_version/$p_new_version/" $patch_file    patch=$( cat $patch_file )    /usr/bin/kubectl patch deployment $app -p $patchelse    /usr/bin/kubectl create -f $deploy_filefi

创建springclouttest service和pod

$ mkdir -p /home/k8s/yml/services/springcloudtest$ cd /home/k8s/yml/services/springcloudtest$ vi springcloudtest-svr.yamlapiVersion: v1kind: Servicemetadata:  name: springcloudtestspec:  ports:  - name: springcloudtest-svr    port: 9091    nodePort: 30000    targetPort: 9091  selector:    app: springcloudtest  type: NodePort$ vi springcloudtest.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: springcloudtestspec:  replicas: 1  template:    metadata:      labels:        app: springcloudtest    spec:      containers:      - name: springclouttest        image: springcloudtest:201805272108        ports:         - containerPort: 9091          protocol: TCP$ vi springcloudtest.patch{"spec":{"template":{"spec":{"containers":[{"name":"springclouttest","image":"springcloudtest:201805052039"}]}}}}

构建jenkins任务:springclouttest

构建完成后,kubectl get pod 查看springcloudtest-1053936621-w19sh   1/1       Running   1          1d

创建springclouttest service

$ cd /home/k8s/yml/services/springcloudtest$ kubectl create -f springcloudtest-svr.yaml查看服务状态$ kubectl get servicespringcloudtest   10.254.126.13   <nodes>       9091:30000/TCP   45m在k8s-node查看容器情况$ docker psCONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES269366fa1b49        mysql:5.6                                                    "docker-entrypoint..."   About an hour ago   Up About an hour                        k8s_mysql.affc4af4_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_404d6f6a2ff21fb61984        springcloudtest:201805272108                                 "/bin/sh -c 'java ..."   About an hour ago   Up About an hour                        k8s_springclouttest.c0ee0b5d_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_a99e1b3788e9ba47faf1        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.389b0ddb_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_7387fdc6130a60acd94d        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.2aaa0bac_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_fa8aba88解决 NodePort方式下,Node所在节点可以访问服务,节点外客户端请求服务都会失败问题$ iptables -P FORWARD ACCEPT

创建数据库和表

$ docker exec -it 269366fa1b49 /bin/bash$ mysql -uroot -p输入密码mysql>create database test1 default charset utf8 COLLATE utf8_general_ci;mysql>use test1mysql>create table HELLO(ID bigint, NAME varchar(128), primary key (ID));

测试服务

浏览器打开 http://10.12.5.115:30000/浏览器显示 Hello World!$ curl --data '{"id":1,"name":"Hello World"}' -H "Content-type: Application/json" http://10.12.5.115:30000/create/hello

【Docker】启动container的时候出现iptables: No chain/target/match by that name

问题

Error response from daemon: driver failed programming external connectivity
 on endpoint jenkins (a8ea15bf9b3dbed599d059d638f79f9dd5e875556c39bfb41e6563d3feedb81b):
  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 50000 -j DNAT
 --to-destination 172.18.0.6:50000 ! -i br-031aa3930383: iptables: No chain/target/match
 by that name.

光看这个报错: iptables: No chain/target/match by that name,就能够看出是跟iptables有关

原因(猜测)

如果再启动docker service的时候网关是关闭的,那么docker管理网络的时候就不会操作网管的配置(chain docker),然后网关重新启动了,导致docker network无法对新container进行网络配置,也就是没有网管的操作权限,做重启处理

处理

service docker restart
或
systemctl docker restart

使用的centos7服务器,在部署docker的过程中,因端口问题有启停firewalld服务,在centos7里使用firewalld代替了iptables。在启动firewalld之后,iptables还会被使用,属于引用的关系。所以在docker run的时候,iptables list里没有docker chain,重启docker engine服务后会被加入到iptables list里面。(有必要深入研究一下docker network)

另一个方法

关闭网关(不建议)

systemctl stop firewalld

systemctl stop iptables

docker – 启动具有多个网络接口的容器

容器启动后,您可以使用 docker network connect 进行操作,但这意味着该进程已经在运行,可能会错过新的.

这个问题是关于码头和多个网络接口的搜索.虽然不是所需的版本在我离开这里的一些信息:

使用Docker 1.12,可以向docker容器添加多个网络接口,但首先需要创建容器,然后在启动容器之前附加第二个(和后续的)网络NIC:

$docker create --network=network1 --name container_name containerimage:latest
$docker network connect network2 container_name
$docker start container_name

需要先创建网络:

$docker network create --driver=bridge network1 --subnet=172.19.0.0/24
$docker network create --driver=bridge network2 --subnet=172.19.1.0/24

此外,您可以使用docker运行中的–network = host参数启动Dockerhost网络接口的容器:

$docker run --net=host containerimage:latest

代码日志版权声明:

翻译自:http://stackoverflow.com/questions/34110416/start-container-with-multiple-network-interfaces

Win10下使用Docker运行redis

在windows下安装一些服务器的开发组件并不是很方便,通常都会用到虚拟机。

Docker 是个好东西,有现成的镜像直接可以使用。不用费时费力在windows上折腾服务器需要的环境。

只要几步就能搞定,非常方便

  1. 注册账号,安装Docker
  2. 获取Redis
  3. 运行Redis
    OK

安装Docker

1、注册一个Docker账号
2、下载并安装Docker

官网下载Docker: https://www.docker.com/get-started

安装完成后,在托盘里有个小鲸鱼图标。在命令行cmd下输入 docker version,可以看到相关信息

D:>docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:21:34 2018
 OS/Arch:           windows/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:29:02 2018
  OS/Arch:          linux/amd64
  Experimental:     false

获取Redis

运行命令行(cmd)工具,直接下载 redis镜像。

d:> docker pull redis

从服务器上拉取redis镜像包,可以使用docker images查看现有的镜像。

D:>docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
redis                      latest              e1a73233e3be        5 weeks ago         83.4MB

运行Redis 并进行端口映射和持久化

一条语句就搞定

docker run -d --name myredis -p6379:6379 -v /d/dockerdata/redis/data:/data redis-server --appendonly yes

参数说明:

  • -d —— 后台运行
  • –name —— 实例运行后的名字 myredis
  • -p6379:6379 —— 端口映射,冒号前面是windows下的端口,后面是虚拟机的端口
  • -v /d/dockerdata/redis/data:/data —— 保存数据的位置。

  • d:dockerdataredisdata 前面是windows下的实际保存数据目录

  • /data 虚拟机内的目录

  • redis-server –appendonly yes —— 在容器执行redis-server启动命令,并打开redis持久化配置。

第一次映射时,会提示需要输入windows的密码,直接输入即可。

查看运行状态和执行命令

执行完成后,可以使用docker ps查看运行状态

D:>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
443ea1afa978        redis               "docker-entrypoint.s…"   About an hour ago   Up About an hour    0.0.0.0:6379->6379/tcp   myredis

可以看到myredis在运行。

在docker下运行redis客户端,就能使用命令来测试

docker exec -it myredis redis-cli

D:>docker exec -it myredis redis-cli
127.0.0.1:6379> info
# Server
redis_version:4.0.11
redis_git_sha1:00000000
redis_git_dirty:0
... ...

重新恢复启动容器

电脑关闭重启或重启docker后,如果没有设置自动启动容器。通过docker ps -a可以看到所有关闭的容器,启动容器。

docker ps -a #看到关闭的容器
docker start your_container_name
docker attach your_container_name

如启动刚才的myredis

D:>docker start myredis
myredis

D:>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
443ea1afa978        redis               "docker-entrypoint.s…"   2 hours ago         Up 5 seconds        0.0.0.0:6379->6379/tcp   myredis

OK 搞定

Docker是个非常棒的东东,解决了不同环境下大量的部署工作。同样方法非常方便的安装其他镜像,如debian、mysql等等。如想安装mysql,使用docker的search命令就能找到一堆。STARS越高使用的人越多,相对比较安全。

D:>docker search mysql
NAME                                                   DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
mysql                                                  MySQL is a widely used, open-source relation…   7119                [OK]
mariadb                                                MariaDB is a community-developed fork of MyS…   2284                [OK]
mysql/mysql-server                                     Optimized MySQL Server Docker images. Create…   521                                     [OK]
zabbix/zabbix-server-mysql                             Zabbix Server with MySQL database support       133                                     [OK]
hypriot/rpi-mysql                                      RPi-compatible Docker Image with Mysql          98

利用Docker搭建FRP内网穿透

去年的时候,我曾经写过一篇关于内网穿透的文章。

内网穿透(Frp)-拯救没有公网IP的你:https://post.smzdm.com/p/566063/

一年过去了,不断的有人咨询我关于使用的问题,而且我发现用这个的人也是越来越多,能问运营商要到公网IP的几率越来越小。我用的移动宽带,客服都直接回复,我们不提供公网IP。另外作者对frp也更新了很多,所以今天想再更新一下关于FRP,来张大妈骗点金币吧。

特别是在路由器、NAS没有公网IP是一件很不方便的事情,尤其是在国内的网络环境。为了解决这个问题,则需要内网穿透,而内网穿透的方法有很多种,例如使用花生壳、NGROK等,但是我感觉frp是相对更为稳定。

frp是什么

frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。

说白了,就是利用一台有公网IP的机子,实现访问你家没有公网IP的设备。

在我使用过的穿透软件中,感觉frp在稳定性、功能和维护方面做得比较好,我也稳定使用了快2年了。

准备工具

从上面的内网穿透的原理可以看出,我么至少需要一台有公网IP的主机,目前来看VPS是最好的选择,以及一个域名。

关于VPS用法、域名购买以及有关工具在我过去的文章中已经详细的说明了。如果对VPS有不明白的地方可以看一下教程 由于我今天只想说说FRP,所以就不赘述了。

域名一定要做泛解析,有时容易忽视这里。不论是二级域名还是三级域名,都可以做泛解析。比如我f.anys.ga做域名泛解析,填入.f就可以吧.f.anys.ga进行解析。

未分类

FRP服务端搭建

利用docker

我选择了Docker方式,好处在于十分方便,容易部署,沙盒机制可以不破坏系统环境。最近docker用的多了感觉十分非常顺手,如果你不喜欢docker可以自行研究。

搭建服务端

我的OS是Ubuntu14.04 x64。首先安装Docker,这里不推荐apt-get 或者yum的形式安装,建议直接从官网下载docker。如果你不关心Docker是什么,那么你也不用深究,如果你有兴趣研究一下,可以自行google。

#安装Docker                                                                                                                                                                  
wget -qO- https://get.docker.com/ | sh

#在宿主机建立目录
mkdir /var/frp && mkdir /var/frp/conf &&cd /var/frp/conf

#获取服务器端配置文件
wget https://frp.anys.ga/frps.ini

未分类

#建立镜像
sudo docker run --name frps -d -v /var/frp/conf:/var/frp/conf -p 5000-5100:5000-5100 -p 7000:7000 -p 7500:7500 -p 7001:7001 -p 8089:80 -p 8443:443 ruiny/frps

未分类

这样服务器就搭建好了。按照以上配置完成的话,服务端口7000,状态查询(web)端口7500,http服务端口8089,https端口为8443,同时开放5000-5100端口用于其他TCP/UDP连接。

之所以用8089/8443端口是因为本机上的80/443端口用给Nginx了,如果你们确保端口没冲突的话可以用80/443端口。(有些强迫症患者只用80端口)

之后登陆你VPS地址IP地址:7500可以查询具体状态,用户名/密码为admin/Qwert123。

未分类

配置文档修改

好了,如果实在懒得动配置,上面的可以正常使用,但一般总要改一下密码什么的吧。我在制作镜像的时候,把配置文件放在了/var/frp/conf下,在上面过去服务器配置的时候,我直接用了作者的官方配置,很多配置是不合适的,如果需要更改:

nano /var/frp/conf/frps.ini

至少你应该修改以下几项内容:(ctrl+x保存,或者习惯用vi编辑器的)

#设置用户名和密码,注意是状态面板的用户名和密码。
dashboard_user = admin
dashboard_pwd = admin
#令牌,很重要,用于验证与客户端的通信。
token = 12345678
#自定义二级域名,如果设置了,之后在客户端的http、https类型的代理中可以不配置custom_domains,而是配置一个 subdomain 参数。可以不配置,默认没有配置。
subdomain_host = frps.com

我只开放了5000-5100用于TCP/UDP端口,如果需要更多端口请修改以下内容。但同时docker run中也要修改-p 5000-5100:5000-5100的端口范围。我测试,如果一次性开放端口过多,会导致docker卡死,所以不建议开太多哦。

# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
allow_ports = 5000-5100

FRP客户端配置

不论是梅林、老毛子或者是群晖,在frp客户端中的设置都大同小异,但是我一般喜欢用配置文档的方式,简单说说配置文档。完整的客户端配置模板可以在查看

配置文档

配置文档分为两部分,第一部分是common开头的通用配置,第二部分就是每个穿透服务项目

未分类

下面将几个例子:

1.主路由web界面

[router]  #主路由web界面
type = http  #穿透类别为http
local_ip = 192.168.1.1  #内网地址
local_port = 80  #穿透服务内网端口
remote_port = 8089
custom_domains = router.f.anys.ga   #外网域名
use_encryption = true #开启加密
use_compression = true #开启压缩

在梅林的界面中对应内容如下:

未分类

2.GEN8中window的远程桌面

[Gen8-Winser]
type = tcp  #类别为tcp
local_ip = 192.168.1.5 内网Gen8的ip地址
local_port = 3389  #注意穿透端口是远程桌面的端口3389
remote_port = 5000 #外网端口随便填一个吧
use_encryption = true
use_compression = true

由于是tcp所以这里不需要外网域名了,这个就类似于端口转发。

未分类

3.DSM黑群晖

[dsm]
type = https #黑群晖DSM的web用https,所以类别是https
local_ip = 192.168.1.7 #黑群晖的地址
local_port = 5001 #群晖的https端口是5001
remote_port = 8443 #外网https端口对应是8443
custom_domains = dsm.f.anys.ga
use_encryption = true
use_compression = true

我黑群晖的内网地址是192.168.1.7,https端口是8443,对应访问域名就是dsm.f.anys.ga:8443。对于https的穿透,需要导入相应证书,否则不会有小绿锁哒~

未分类

群晖和梅林都有导入的地方,比如群晖在这里:

未分类

frp的作者还搞了一些高级用法,特殊情况可以尝试用一下:

4.转发 DNS 查询请求

可以通过frp转发自己的DSN请求,在客户端配置文件中设置。将DNS请求转发到google的8.8.8.8

[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000

5.stcp 类型的代理

stcp利用了一个sk验证,使得只有sk一直的两个客户端能够点对点互访,防止公网上的随意访问。

家里的路由器:

[secret_ssh]
type = stcp #类型是stcp
sk = router1 # 只有 sk 一致的用户才能访问到此服务
local_ip = 192.168.1.1
local_port = 22

另外一台PC:

[secret_ssh_visitor]
type = stcp
role = visitor # 访问者
server_name = secret_ssh # 对应穿透服务的名称与上面一直
sk = router1 # 只有 sk 一致的用户才能访问到此服务
bind_addr = 127.0.0.1 
bind_port = 8022 # 绑定本地的端口

在本地访问ssh,地址127.0.0.1,端口8022就会穿透到家里的路由器22端口。

未分类

测试

一个不知道什么时候就会消失的测试服务器,注意人多容易重名,不要当作正式用途。

Web地址:网址   admin/Qwert123
FRP服务地址:f.anys.ga:7000 
token:f.anys.ga 
TCP/UDP端口:8089,443, 5000-5100
三级域名 *.f.anys.ga

其它问题

1.修改frps.ini后需要重新启动容器 docker restart frps

2.服务器端只适用于64位的系统

3.0.17以后的版本和之前的不兼容,注意客户端和服务器版本一致

4.由于frp是封装在docker里面运行的,如果要讨论效率的话我不好说。

说真的FRP真的很好用,之前用Vultr做服务器的时候,带宽足够可以在线看家里NAS的视频,而且非常稳定,不论是客户端还是服务端都很稳,基本不用操心。

『高级篇』docker之微服务业务分析(九)

从本节开始微服务的开发,说到开发有几个问题需要解决,首先要知道我们需要开发什么?什么样的业务场景,分析业务场景,有几个微服务,每个微服务需要完成什么样的功能,微服务之间的关系,之间的依赖关系,他们之间是如何通迅的,这些都了解的之后,我们就可以进入开发阶段了。

业务场景

  • 用户可以注册和登录

现在的登录系统一般都是单点登录,支持跨域,在去使用其他系统的时候就不需要登录了,最好是不要使用session,最好是无状态的,避免使用session。

  • 登录用户可以对课程进行CURD操作

上边这个不是大而全的系统,只是微服务的功能,老铁咱们的目的很明确是搞微服务,不是学web开发的,我们通过上边的几个功能上从0开始了解微服务,一行不拉的完成开发微服务,让大家去开发有个真切的体会的。

基本的微服务的流程

  1. 用户访问api网关
  2. api网关访问2个用户的edgeservice(java) 和 课程的edgeservice(java)
  3. 这2个接口提供的http协议(rest)
  4. 用户的edgeservice 访问的用户服务
  5. 课程的edgeservice 访问的课程服务
  6. 用户服务对外接口是Thrift 跨语言的协议
  7. 课程服务对外接口使用Dubbo,只限java
  8. 用户服务(java) 后端有个数据库
  9. 课程服务(java) 后端也有一个数据库
  10. 信息服务(python)对外接口是Thrift (用户注册的时候,可能发送短信,或者邮件确认可能需要信息服务)
  11. redis 来进行用户的信息存储,提供给用户服务使用
  12. 课程的edgeservice 和 课程服务 通过 zookeeper 来完成服务的发现
  13. 用户查询课程 需要调用课程服务,这也就是微服务之间的调用

未分类