精华内容
下载资源
问答
  • 2018-10-14 23:06:30

    转载自:https://www.cnblogs.com/fengrui-/p/6074381.html
    一、简介
    在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象,它们可以通过Controller 的方法参数灵活的获取到。为了先对Controller 有一个初步的印象,以下先定义一个简单的Controller :

    @Controller
    public class MyController {
    
        @RequestMapping ( "/showView" )
        public ModelAndView showView() {
           ModelAndView modelAndView = new ModelAndView();
           modelAndView.setViewName( "viewName" );
           modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " );
           return modelAndView;
        }
    
    }
    

    在上面的示例中,@Controller 是标记在类MyController 上面的,所以类MyController 就是一个SpringMVC Controller 对象了,然后使用@RequestMapping(“/showView”) 标记在Controller 方法上,表示当请求/showView.do 的时候访问的是MyController 的showView 方法,该方法返回了一个包括Model 和View 的ModelAndView 对象。
    二、使用 @Controller 定义一个 Controller 控制器
    @Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器,这个接下来就会讲到。

    单单使用@Controller 标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。拿MyController 来举一个例子

    @Controller
    public class MyController {
        @RequestMapping ( "/showView" )
        public ModelAndView showView() {
           ModelAndView modelAndView = new ModelAndView();
           modelAndView.setViewName( "viewName" );
           modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " );
           return modelAndView;
        }
    
    }
    

    这个时候有两种方式可以把MyController 交给Spring 管理,好让它能够识别我们标记的@Controller 。

    第一种方式是在SpringMVC 的配置文件中定义MyController 的bean 对象。

    <bean class="com.host.app.web.controller.MyController"/>
    

    第二种方式是在SpringMVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller 控制器。

     < context:component-scan base-package = "com.host.app.web.controller" >
           < context:exclude-filter type = "annotation"
               expression = "org.springframework.stereotype.Service" />
        </ context:component-scan > 
    

    注:上面 context:exclude-filter 标注的是不扫描 @Service 标注的类

    三、使用 @RequestMapping 来映射 Request 请求与处理器
    可以使用@RequestMapping 来映射URL 到控制器类,或者是到Controller 控制器的处理方法上。当@RequestMapping 标记在Controller 类上的时候,里面使用@RequestMapping 标记的方法的请求地址都是相对于类上的@RequestMapping 而言的;当Controller 类上没有标记@RequestMapping 注解时,方法上的@RequestMapping 都是绝对路径。这种绝对路径和相对路径所组合成的最终路径都是相对于根路径“/ ”而言的。

    @Controller
    public class MyController {
        @RequestMapping ( "/showView" )
        public ModelAndView showView() {
           ModelAndView modelAndView = new ModelAndView();
           modelAndView.setViewName( "viewName" );
           modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " );
           return modelAndView;
        }
    
    }
    

    在这个控制器中,因为MyController 没有被@RequestMapping 标记,所以当需要访问到里面使用了@RequestMapping 标记的showView 方法时,就是使用的绝对路径/showView.do 请求就可以了。

    @Controller
    @RequestMapping ( "/test" )
    public class MyController {
        @RequestMapping ( "/showView" )
        public ModelAndView showView() {
           ModelAndView modelAndView = new ModelAndView();
           modelAndView.setViewName( "viewName" );
           modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " );
           return modelAndView;
        }
    
    }
    

    这种情况是在控制器上加了@RequestMapping 注解,所以当需要访问到里面使用了@RequestMapping 标记的方法showView() 的时候就需要使用showView 方法上@RequestMapping 相对于控制器MyController上@RequestMapping 的地址,即/test/showView.do 。

    (一)使用 URI 模板
    URI 模板就是在URI 中给定一个变量,然后在映射的时候动态的给该变量赋值。如URI 模板http://localhost/app/{variable1}/index.html ,这个模板里面包含一个变量variable1 ,那么当我们请求http://localhost/app/hello/index.html 的时候,该URL 就跟模板相匹配,只是把模板中的variable1 用hello 来取代。在SpringMVC 中,这种取代模板中定义的变量的值也可以给处理器方法使用,这样我们就可以非常方便的实现URL 的RestFul 风格。这个变量在SpringMVC 中是使用@PathVariable 来标记的。

    在SpringMVC 中,我们可以使用@PathVariable 来标记一个Controller 的处理方法参数,表示该参数的值将使用URI 模板中对应的变量的值来赋值。

    @Controller
    @RequestMapping ( "/test/{variable1}" )
    public class MyController {
    
        @RequestMapping ( "/showView/{variable2}" )
        public ModelAndView showView( @PathVariable String variable1, @PathVariable ( "variable2" ) int variable2) {
           ModelAndView modelAndView = new ModelAndView();
           modelAndView.setViewName( "viewName" );
           modelAndView.addObject( " 需要放到 model 中的属性名称 " , " 对应的属性值,它是一个对象 " );
           return modelAndView;
        }
    }
    

    在上面的代码中我们定义了两个URI 变量,一个是控制器类上的variable1 ,一个是showView 方法上的variable2,然后在showView 方法的参数里面使用@PathVariable 标记使用了这两个变量。所以当我们使用/test/hello/showView/2.do 来请求的时候就可以访问到MyController 的showView 方法,这个时候variable1 就被赋予值hello ,variable2 就被赋予值2 ,然后我们在showView 方法参数里面标注了参数variable1 和variable2 是来自访问路径的path 变量,这样方法参数variable1 和variable2 就被分别赋予hello 和2 。方法参数variable1 是定义为String 类型,variable2 是定义为int 类型,像这种简单类型在进行赋值的时候Spring 是会帮我们自动转换的

    在上面的代码中我们可以看到在标记variable1 为path 变量的时候我们使用的是@PathVariable ,而在标记variable2 的时候使用的是@PathVariable(“variable2”) 。这两者有什么区别呢?第一种情况就默认去URI 模板中找跟参数名相同的变量,但是这种情况只有在使用debug 模式进行编译的时候才可以,而第二种情况是明确规定使用的就是URI 模板中的variable2 变量。当不是使用debug 模式进行编译,或者是所需要使用的变量名跟参数名不相同的时候,就要使用第二种方式明确指出使用的是URI 模板中的哪个变量。

    除了在请求路径中使用URI 模板,定义变量之外,@RequestMapping 中还支持通配符“* ”。如下面的代码我就可以使用/myTest/whatever/wildcard.do 访问到Controller 的testWildcard 方法。

    @Controller
    @RequestMapping ( "/myTest" )
    public class MyController {
        @RequestMapping ( "*/wildcard" )
        public String testWildcard() {
           System. out .println( "wildcard------------" );
           return "wildcard" ;
        }  
    }
    

    (二)使用 @RequestParam 绑定 HttpServletRequest 请求参数到控制器方法参数

    @RequestMapping ( "requestParam" )
       public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
           return "requestParam" ;
       } 
    

    在上面代码中利用@RequestParam 从HttpServletRequest 中绑定了参数name 到控制器方法参数name ,绑定了参数age 到控制器方法参数age 。值得注意的是和@PathVariable 一样,当你没有明确指定从request 中取哪个参数时,Spring 在代码是debug 编译的情况下会默认取更方法参数同名的参数,如果不是debug 编译的就会报错。此外,当需要从request 中绑定的参数和方法的参数名不相同的时候,也需要在@RequestParam 中明确指出是要绑定哪个参数。在上面的代码中如果我访问/requestParam.do?name=hello&age=1 则Spring 将会把request请求参数name 的值hello 赋给对应的处理方法参数name ,把参数age 的值1 赋给对应的处理方法参数age 。

    在@RequestParam 中除了指定绑定哪个参数的属性value 之外,还有一个属性required ,它表示所指定的参数是否必须在request 属性中存在,默认是true ,表示必须存在,当不存在时就会报错。在上面代码中我们指定了参数name 的required 的属性为false ,而没有指定age 的required 属性,这时候如果我们访问/requestParam.do而没有传递参数的时候,系统就会抛出异常,因为age 参数是必须存在的,而我们没有指定。而如果我们访问/requestParam.do?age=1 的时候就可以正常访问,因为我们传递了必须的参数age ,而参数name 是非必须的,不传递也可以。

    更多相关内容
  • 假定两个Controller都在同一个工程中。 如果有比较合理的分层设计,这样的需求应该是非常罕见的。因为大部分情况下,调用应该都是限于对业务逻辑层或者数据库层,并不会涉及Controller之间的调用。 本文是讨论...
  • 本文介绍了Grbl Controller安装与使用教程,是使用Grbl Controller的入门基础必读文件。
  • USB-Serial Controller D USB转串口驱动 免费版,亲测可以用
  • amd sata controller(AMD SATA控制器)

    热门讨论 2018-03-06 19:57:23
    Windows10系统不能识别新增光驱位机械硬盘的解决方法,安装amd_sata_controller驱动程序 。 右键点击系统桌面左下角的【开始】,在开始菜单中点击【设备管理器(M)】
  • SSM框架下基本的mapper,dao,service,controller等相关文件的整理
  • Realtek PCIe GBE Family Controller 网卡驱动 for Linux
  • 闭路摄像头驱动usb 2.0 video capture controller
  • <code>Test1Controller中下面这个接口 @ApiOperation(value = "测试接口", notes = "测试接口", response = BaseResult.class, responseContainer = "Map") @...
  • SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了,然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾,回顾一下...重点讲讲Controller的传参

    SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了,然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾,回顾一下springMVC中的一些常用的功能。

    按照正常的流程,我们应该先讲一讲怎么配置视图解析器,但是已经提到了,现在都是前后端分离的项目,后端无需配置页面跳转,只需要返回数据即可,所以这一部分也没必要再讲了。那咱么就介绍下Controller中的常用写法。

    上一次我们介绍了 @RestController 注解,这个注解会把所有的返回结果以json(json其实不太准确,应该是可序列化对象,大部分是json形式)的形式进行返回。我们今天来举个例子。

    5.1关于@RestController注解

    Controller代码:

    @RestController
    public class SecondController {
    
        @RequestMapping("/second")
        public User second(){
            User u = new User("张胜男", 30,"女");
            return u;
        }
    }
    

    这里返回了一个User类。 User类代码如下:

    package com.lsqingfeng.springboot.vo;
    
    /**
     * @className: User
     * @description:
     * @author: sh.Liu
     * @date: 2022-01-12 11:25
     */
    public class User {
    
        private String name;
    
        private Integer age;
    
        private String gender;
    
        public User(String name, Integer age, String gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    }
    

    访问:http://localhost:9090/second 得到结果:

    看到了返回的就是json类型的数据。这就是 @RestController的作用。如果不用@RestController,我们在方法上加上@ResponseBody是同样的效果。 @RestController注解就是@Controller和@ResponseBody 的组合。

    5.2 关于@RequestMapping注解

    @RequestMapping注解主要用于标识该Controller的请求地址路径。比如上面路径中的@RequestMapping("/second") 就代表通过 /second可以访问到这个接口。这个注解既可以放在类上,也可以放到方法上,如果类上也有这个注解,那么类中所有的controller在访问的时候,都要带上类上的路径才可以访问到。比如:

    @RestController
    @RequestMappin("/test")
    public class SecondController {
    
        @RequestMapping("/second")
        public User second(){
            User u = new User("张胜男", 30,"女");
            return u;
        }
        
        @RequestMapping("/third")
        public User third(){
            User u = new User("张胜", 50, "男");
            return u;
        }
    }
    

    比如上面的Controller, 那么我们访问的路径就变成了

    ip:port/test/second ip:port/test/third

    并且这也是现在最常用的方式,相当于类上的注解里的url作为一个大的分类(一般代表一个模块),方法是模块中的各个功能。

    同时还要注意,如果方法上面使用了@RequestMapping 注解来进行标记,那么其实并没有限定这个方法用Http的哪种请求方式来进行访问,所以我们可以用Get请求,也可以用post请求。当然除了这些还有一下不太常用的Delete,put,optio等请求方式。比如我们最上边那个例子:

    Get请求访问:

    Post请求访问:

    Delete请求访问:

    但是一般情况下,我们都会限定这个接口只能一某一种请求方式访问,如何限制呢?

    方法一: 通过RequestMapping中的参数

    @RestController
    public class SecondController {
    
        @RequestMapping(value = "/second", method = RequestMethod.GET)
        public User second(){
            User u = new User("张胜男", 30,"女");
            return u;
        }
    }
    

    这时候就只能用Get请求来访问了。

    其他请求方式会报错.

    方法二: 使用限定请求方式的注解:

    如果限定Get请求方式, 用@GetMapping 代替 @RequestMapping\

    如果限定Post请求方式, 用@PostMapping 代替 @RequestMapping

    比如限定Post请求方式:

    @RestController
    public class SecondController {
    
        @PostMapping("second")
        public User second(){
            User u = new User("张胜男", 30,"女");
            return u;
        }
    }
    

    我们可以看下 @PostMapping注解的实现方式,其实跟方式一是一样的。

    5.3关于Controller中传参

    Get请求传参:

    Get请求的参数传递都是通过在url后面拼接来进行传参的, 比如我们传一个name 和age 。url的写法如下:

    localhost:9090/third?name=zhangsan&age=20

    在请求的路径?后面表示参数,多个参数之间用 & 进行连接。 那么在Controller中如何接收Get请求传递过来的参数呢。方式很多,我们就说两种最常用的

    1. 直接在Controller方法的参数中,用相同的变量名称接收参数。以上面的请求为例
    @GetMapping("third")
    public String third(String name, Integer age){
        System.out.println(name);
        System.out.println(age);
        return "success";
    }
    

    成功获取参数:

    1. 如果参数太多,用上面的方式来写,会导致方法中的参数过多,看起来很乱。所以我们也可以使用一个javaBean来接收。前期就是javaBean中的属性名要和参数名相同,同时要有get和set方法。
    @GetMapping("getParam")
    public User getParam(User user){
        System.out.println(user);
        // 将接收到的参数直接返回
        return user;
    }
    

    User类,再给一下:

    package com.lsqingfeng.springboot.vo;
    
    /**
     * @className: User
     * @description:
     * @author: sh.Liu
     * @date: 2022-01-12 11:25
     */
    public class User {
    
        private String name;
    
        private Integer age;
    
        private String gender;
    
        public User(String name, Integer age, String gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    ", age=" + age +
                    ", gender='" + gender + ''' +
                    '}';
        }
    }
    

    访问: http://localhost:9090/getParam?name=zhangsan&age=20

    gender没有传值,所以是null, 参数接收成功:

    Post请求传参

    Post请求传参的方式,常用的有两种,一中是通过form表单进行传参(就是html中的form),还有一种是通过json的形式传参。目前来说用json的比较多。

    1. Form传参

    使用form传参,我们的Controller再接收的时候和上面一样可以直接接收上代码。

    @PostMapping("postForm")
    public User postForm(String name, Integer age){
        User u = new User(name, age, null);
        return u;
    }
    

    post请求需要通过postman来模拟了,注意form传参的位置,要在body的form-data里

    如果参数过多,我们也是可以通过一个javaBean来进行接收的。

    @PostMapping("postForm2")
    public User postForm2(User user){
        return user;
    }
    

    2. Json 传参

    通过json传递的参数,content-type一般为: application/json

    我们在接收参数的时候要通过一个新的注解 @RequestBody 来进行标识。

    // 注意,User中一定要有无参构造方法
    @PostMapping("postJson")
    public User postJson(@RequestBody User user){
        return user;
    }
    

    通过json方式请求,看是否成功获取。

    3. 关于@RequestParam注解

    还有一种方式,这种方式不是很常用,但有的时候,跟三方接口对接的时候,不排除他们使用这种方式。

    就是针对content-type是: application/x-www-form-urlcoded (Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)这是我在网上的资料中找到的,也验证了,如下:

    @PostMapping("postWWWForm")
    public User postWWWForm(@RequestParam String name, @RequestParam Integer  age){
        return new User(name, age, null);
    }
    

    后来我把@RequestParam 注解去掉了,发现居然也可以接收到参数。其实这个注解就是用来接收普通参数的一个注解。正常情况下应该是可以省略的。什么时候不能省略呢,就是有时候这个参数是必填项,就是必须要传,那么我们可以在这个注解中标识是否必传和默认值。

    @RequestParam(value="id",required =false,defaultValue ="10086") Integer id,

    默认是true.

    好了关于SpringBoot中Controller的传参方式我们就介绍这么多。如有错误也欢迎大家交流指正。

    展开全文
  • k8s自定义controller三部曲之三:编写controller代码

    万次阅读 多人点赞 2019-04-01 00:15:25
    本文是《k8s自定义controller三部曲》的终篇,编写controller的代码,通过监听API对象的增删改变化做出业务方面的响应

    本文是《k8s自定义controller三部曲》的终篇,前面的章节中,我们创建了CRD,再通过自动生成代码的工具将controller所需的informer、client等依赖全部准备好,到了本章,就该编写controller的代码了,也就是说,现在已经能监听到Student对象的增删改等事件,接下来就是根据这些事件来做不同的事情,满足个性化的业务需求;

    三部曲所有文章链接

    1. 《k8s自定义controller三部曲之一:创建CRD(Custom Resource Definition)》
    2. 《k8s自定义controller三部曲之二:自动生成代码》
    3. 《k8s自定义controller三部曲之三:编写controller代码》

    源码下载

    接下来详细讲述应用的编码过程,如果您不想自己写代码,也可以在GitHub下载完整的应用源码,地址和链接信息如下表所示:

    名称链接备注
    项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
    git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
    git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议

    这个git项目中有多个文件夹,本章源码在k8s_customize_controller这个文件夹下,如下图红框所示:
    在这里插入图片描述

    开始实战

    1. 回顾一下,上一章通过自动代码生成工具生成代码后,源码目录的内容如下:
    [root@golang k8s_customize_controller]# tree
    .
    └── pkg
        ├── apis
        │   └── bolingcavalry
        │       ├── register.go
        │       └── v1
        │           ├── doc.go
        │           ├── register.go
        │           ├── types.go
        │           └── zz_generated.deepcopy.go
        └── client
            ├── clientset
            │   └── versioned
            │       ├── clientset.go
            │       ├── doc.go
            │       ├── fake
            │       │   ├── clientset_generated.go
            │       │   ├── doc.go
            │       │   └── register.go
            │       ├── scheme
            │       │   ├── doc.go
            │       │   └── register.go
            │       └── typed
            │           └── bolingcavalry
            │               └── v1
            │                   ├── bolingcavalry_client.go
            │                   ├── doc.go
            │                   ├── fake
            │                   │   ├── doc.go
            │                   │   ├── fake_bolingcavalry_client.go
            │                   │   └── fake_student.go
            │                   ├── generated_expansion.go
            │                   └── student.go
            ├── informers
            │   └── externalversions
            │       ├── bolingcavalry
            │       │   ├── interface.go
            │       │   └── v1
            │       │       ├── interface.go
            │       │       └── student.go
            │       ├── factory.go
            │       ├── generic.go
            │       └── internalinterfaces
            │           └── factory_interfaces.go
            └── listers
                └── bolingcavalry
                    └── v1
                        ├── expansion_generated.go
                        └── student.go
    
    21 directories, 27 files
    
    1. 本章要编写的第一个go文件就是controller.go,在k8s_customize_controller目录下创建controller.go,代码内容如下:
    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/golang/glog"
    	corev1 "k8s.io/api/core/v1"
    	"k8s.io/apimachinery/pkg/api/errors"
    	"k8s.io/apimachinery/pkg/util/runtime"
    	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    	"k8s.io/apimachinery/pkg/util/wait"
    	"k8s.io/client-go/kubernetes"
    	"k8s.io/client-go/kubernetes/scheme"
    	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    	"k8s.io/client-go/tools/cache"
    	"k8s.io/client-go/tools/record"
    	"k8s.io/client-go/util/workqueue"
    
    	bolingcavalryv1 "github.com/zq2599/k8s-controller-custom-resource/pkg/apis/bolingcavalry/v1"
    	clientset "github.com/zq2599/k8s-controller-custom-resource/pkg/client/clientset/versioned"
    	studentscheme "github.com/zq2599/k8s-controller-custom-resource/pkg/client/clientset/versioned/scheme"
    	informers "github.com/zq2599/k8s-controller-custom-resource/pkg/client/informers/externalversions/bolingcavalry/v1"
    	listers "github.com/zq2599/k8s-controller-custom-resource/pkg/client/listers/bolingcavalry/v1"
    )
    
    const controllerAgentName = "student-controller"
    
    const (
    	SuccessSynced = "Synced"
    
    	MessageResourceSynced = "Student synced successfully"
    )
    
    // Controller is the controller implementation for Student resources
    type Controller struct {
    	// kubeclientset is a standard kubernetes clientset
    	kubeclientset kubernetes.Interface
    	// studentclientset is a clientset for our own API group
    	studentclientset clientset.Interface
    
    	studentsLister listers.StudentLister
    	studentsSynced cache.InformerSynced
    
    	workqueue workqueue.RateLimitingInterface
    
    	recorder record.EventRecorder
    }
    
    // NewController returns a new student controller
    func NewController(
    	kubeclientset kubernetes.Interface,
    	studentclientset clientset.Interface,
    	studentInformer informers.StudentInformer) *Controller {
    
    	utilruntime.Must(studentscheme.AddToScheme(scheme.Scheme))
    	glog.V(4).Info("Creating event broadcaster")
    	eventBroadcaster := record.NewBroadcaster()
    	eventBroadcaster.StartLogging(glog.Infof)
    	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
    	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
    
    	controller := &Controller{
    		kubeclientset:    kubeclientset,
    		studentclientset: studentclientset,
    		studentsLister:   studentInformer.Lister(),
    		studentsSynced:   studentInformer.Informer().HasSynced,
    		workqueue:        workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Students"),
    		recorder:         recorder,
    	}
    
    	glog.Info("Setting up event handlers")
    	// Set up an event handler for when Student resources change
    	studentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    		AddFunc: controller.enqueueStudent,
    		UpdateFunc: func(old, new interface{}) {
    			oldStudent := old.(*bolingcavalryv1.Student)
    			newStudent := new.(*bolingcavalryv1.Student)
    			if oldStudent.ResourceVersion == newStudent.ResourceVersion {
                    //版本一致,就表示没有实际更新的操作,立即返回
    				return
    			}
    			controller.enqueueStudent(new)
    		},
    		DeleteFunc: controller.enqueueStudentForDelete,
    	})
    
    	return controller
    }
    
    //在此处开始controller的业务
    func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
    	defer runtime.HandleCrash()
    	defer c.workqueue.ShutDown()
    
    	glog.Info("开始controller业务,开始一次缓存数据同步")
    	if ok := cache.WaitForCacheSync(stopCh, c.studentsSynced); !ok {
    		return fmt.Errorf("failed to wait for caches to sync")
    	}
    
    	glog.Info("worker启动")
    	for i := 0; i < threadiness; i++ {
    		go wait.Until(c.runWorker, time.Second, stopCh)
    	}
    
    	glog.Info("worker已经启动")
    	<-stopCh
    	glog.Info("worker已经结束")
    
    	return nil
    }
    
    func (c *Controller) runWorker() {
    	for c.processNextWorkItem() {
    	}
    }
    
    // 取数据处理
    func (c *Controller) processNextWorkItem() bool {
    
    	obj, shutdown := c.workqueue.Get()
    
    	if shutdown {
    		return false
    	}
    
    	// We wrap this block in a func so we can defer c.workqueue.Done.
    	err := func(obj interface{}) error {
    		defer c.workqueue.Done(obj)
    		var key string
    		var ok bool
    
    		if key, ok = obj.(string); !ok {
    
    			c.workqueue.Forget(obj)
    			runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
    			return nil
    		}
    		// 在syncHandler中处理业务
    		if err := c.syncHandler(key); err != nil {
    			return fmt.Errorf("error syncing '%s': %s", key, err.Error())
    		}
    
    		c.workqueue.Forget(obj)
    		glog.Infof("Successfully synced '%s'", key)
    		return nil
    	}(obj)
    
    	if err != nil {
    		runtime.HandleError(err)
    		return true
    	}
    
    	return true
    }
    
    // 处理
    func (c *Controller) syncHandler(key string) error {
    	// Convert the namespace/name string into a distinct namespace and name
    	namespace, name, err := cache.SplitMetaNamespaceKey(key)
    	if err != nil {
    		runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))
    		return nil
    	}
    
    	// 从缓存中取对象
    	student, err := c.studentsLister.Students(namespace).Get(name)
    	if err != nil {
    		// 如果Student对象被删除了,就会走到这里,所以应该在这里加入执行
    		if errors.IsNotFound(err) {
    			glog.Infof("Student对象被删除,请在这里执行实际的删除业务: %s/%s ...", namespace, name)
    
    			return nil
    		}
    
    		runtime.HandleError(fmt.Errorf("failed to list student by: %s/%s", namespace, name))
    
    		return err
    	}
    
    	glog.Infof("这里是student对象的期望状态: %#v ...", student)
    	glog.Infof("实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除)")
    
    	c.recorder.Event(student, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
    	return nil
    }
    
    // 数据先放入缓存,再入队列
    func (c *Controller) enqueueStudent(obj interface{}) {
    	var key string
    	var err error
    	// 将对象放入缓存
    	if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
    		runtime.HandleError(err)
    		return
    	}
    
    	// 将key放入队列
    	c.workqueue.AddRateLimited(key)
    }
    
    // 删除操作
    func (c *Controller) enqueueStudentForDelete(obj interface{}) {
    	var key string
    	var err error
    	// 从缓存中删除指定对象
    	key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
    	if err != nil {
    		runtime.HandleError(err)
    		return
    	}
    	//再将key放入队列
    	c.workqueue.AddRateLimited(key)
    }
    

    上述代码有以下几处关键点:
    a. 创建controller的NewController方法中,定义了收到Student对象的增删改消息时的具体处理逻辑,除了同步本地缓存,就是将该对象的key放入消息中;
    b. 实际处理消息的方法是syncHandler,这里面可以添加实际的业务代码,来响应Student对象的增删改情况,达到业务目的;

    3. 接下来可以写main.go了,不过在此之前把处理系统信号量的辅助类先写好,然后在main.go中会用到(处理例如ctrl+c的退出),在$GOPATH/src/k8s_customize_controller/pkg目录下新建目录signals
    4. 在signals目录下新建文件signal_posix.go:

    // +build !windows
    
    package signals
    
    import (
    	"os"
    	"syscall"
    )
    
    var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
    
    1. 在signals目录下新建文件signal_posix.go:
    package signals
    
    import (
    	"os"
    )
    
    var shutdownSignals = []os.Signal{os.Interrupt}
    
    1. 在signals目录下新建文件signal.go:
    package signals
    
    import (
            "os"
            "os/signal"
    )
    
    var onlyOneSignalHandler = make(chan struct{})
    
    func SetupSignalHandler() (stopCh <-chan struct{}) {
            close(onlyOneSignalHandler) // panics when called twice
    
            stop := make(chan struct{})
            c := make(chan os.Signal, 2)
            signal.Notify(c, shutdownSignals...)
            go func() {
                    <-c
                    close(stop)
                    <-c
                    os.Exit(1) // second signal. Exit directly.
            }()
    
            return stop
    }
    
    1. 接下来可以编写main.go了,在k8s_customize_controller目录下创建main.go文件,内容如下,关键位置已经加了注释,就不再赘述了:
    package main
    
    import (
    	"flag"
    	"time"
    
    	"github.com/golang/glog"
    	"k8s.io/client-go/kubernetes"
    	"k8s.io/client-go/tools/clientcmd"
    	// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
    	// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    
    	clientset "k8s_customize_controller/pkg/client/clientset/versioned"
    	informers "k8s_customize_controller/pkg/client/informers/externalversions"
    	"k8s_customize_controller/pkg/signals"
    )
    
    var (
    	masterURL  string
    	kubeconfig string
    )
    
    func main() {
    	flag.Parse()
    
    	// 处理信号量
    	stopCh := signals.SetupSignalHandler()
    
        // 处理入参
    	cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
    	if err != nil {
    		glog.Fatalf("Error building kubeconfig: %s", err.Error())
    	}
    
    	kubeClient, err := kubernetes.NewForConfig(cfg)
    	if err != nil {
    		glog.Fatalf("Error building kubernetes clientset: %s", err.Error())
    	}
    
    	studentClient, err := clientset.NewForConfig(cfg)
    	if err != nil {
    		glog.Fatalf("Error building example clientset: %s", err.Error())
    	}
    
    	studentInformerFactory := informers.NewSharedInformerFactory(studentClient, time.Second*30)
    
        //得到controller
    	controller := NewController(kubeClient, studentClient,
    		studentInformerFactory.Bolingcavalry().V1().Students())
    
        //启动informer
    	go studentInformerFactory.Start(stopCh)
    
        //controller开始处理消息
    	if err = controller.Run(2, stopCh); err != nil {
    		glog.Fatalf("Error running controller: %s", err.Error())
    	}
    }
    
    func init() {
    	flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
    	flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
    }
    

    至此,所有代码已经编写完毕,接下来是编译构建;

    编译构建和启动

    1. 在$GOPATH/src/k8s_customize_controller目录下,执行以下命令:
    go get k8s.io/client-go/kubernetes/scheme \
    && go get github.com/golang/glog \
    && go get k8s.io/kube-openapi/pkg/util/proto \
    && go get k8s.io/utils/buffer \
    && go get k8s.io/utils/integer \
    && go get k8s.io/utils/trace
    
    1. 上述脚本将编译过程中依赖的库通过go get方式进行获取,属于笨办法,更好的方法是选用一种包依赖工具,具体的可以参照k8s的官方demo,这个代码中同时提供了godep和vendor两种方式来处理上面的包依赖问题,地址是:https://github.com/kubernetes/sample-controller
    2. 解决了包依赖问题后,在$GOPATH/src/k8s_customize_controller目录下执行命令go build,即可在当前目录生成k8s_customize_controller文件;
    3. 将文件k8s_customize_controller复制到k8s环境中,记得通过chmod a+x命令给其可执行权限;
    4. 执行命令./k8s_customize_controller -kubeconfig=$HOME/.kube/config -alsologtostderr=true,会立即启动controller,看到控制台输出如下:
    [root@master 31]# ./k8s_customize_controller -kubeconfig=$HOME/.kube/config -alsologtostderr=true
    I0331 23:27:17.909265   21540 controller.go:72] Setting up event handlers
    I0331 23:27:17.909450   21540 controller.go:96] 开始controller业务,开始一次缓存数据同步
    I0331 23:27:18.110448   21540 controller.go:101] worker启动
    I0331 23:27:18.110516   21540 controller.go:106] worker已经启动
    I0331 23:27:18.110653   21540 controller.go:181] 这里是student对象的期望状态: &v1.Student{TypeMeta:v1.TypeMeta{Kind:"Student", APIVersion:"bolingcavalry.k8s.io/v1"}, ObjectMeta:v1.ObjectMeta{Name:"object-student", GenerateName:"", Namespace:"default", SelfLink:"/apis/bolingcavalry.k8s.io/v1/namespaces/default/students/object-student", UID:"92927d0d-5360-11e9-9d2a-000c29f1f9c9", ResourceVersion:"310395", Generation:1, CreationTimestamp:v1.Time{Time:time.Time{wall:0x0, ext:63689597785, loc:(*time.Location)(0x1f9c200)}}, DeletionTimestamp:(*v1.Time)(nil), DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"bolingcavalry.k8s.io/v1\",\"kind\":\"Student\",\"metadata\":{\"annotations\":{},\"name\":\"object-student\",\"namespace\":\"default\"},\"spec\":{\"name\":\"张三\",\"school\":\"深圳中学\"}}\n"}, OwnerReferences:[]v1.OwnerReference(nil), Initializers:(*v1.Initializers)(nil), Finalizers:[]string(nil), ClusterName:"", ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1.StudentSpec{name:"", school:""}} ...
    I0331 23:27:18.111105   21540 controller.go:182] 实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除)
    I0331 23:27:18.111187   21540 controller.go:145] Successfully synced 'default/object-student'
    I0331 23:27:18.112263   21540 event.go:209] Event(v1.ObjectReference{Kind:"Student", Namespace:"default", Name:"object-student", UID:"92927d0d-5360-11e9-9d2a-000c29f1f9c9", APIVersion:"bolingcavalry.k8s.io/v1", ResourceVersion:"310395", FieldPath:""}): type: 'Normal' reason: 'Synced' Student synced successfully
    

    至此,自定义controller已经启动成功了,并且从缓存中获取到了上一章中创建的对象的信息,接下来我们在k8s环境对Student对象做增删改,看看controller是否能做出响应;

    验证controller

    1. 新开一个窗口连接到k8s环境,新建一个名为new-student.yaml的文件,内容如下:
    apiVersion: bolingcavalry.k8s.io/v1
    kind: Student
    metadata:
      name: new-student
    spec:
      name: "李四"
      school: "深圳小学"
    
    1. 在new-student.yaml所在目录执行命令kubectl apply -f new-student.yaml
    2. 返回controller所在的控制台窗口,发现新输出了如下内容,可见新增student对象的事件已经被controller监听并处理:
    I0331 23:43:03.789894   21540 controller.go:181] 这里是student对象的期望状态: &v1.Student{TypeMeta:v1.TypeMeta{Kind:"", APIVersion:""}, ObjectMeta:v1.ObjectMeta{Name:"new-student", GenerateName:"", Namespace:"default", SelfLink:"/apis/bolingcavalry.k8s.io/v1/namespaces/default/students/new-student", UID:"abcd77d6-53cb-11e9-9d2a-000c29f1f9c9", ResourceVersion:"370653", Generation:1, CreationTimestamp:v1.Time{Time:time.Time{wall:0x0, ext:63689643783, loc:(*time.Location)(0x1f9c200)}}, DeletionTimestamp:(*v1.Time)(nil), DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"bolingcavalry.k8s.io/v1\",\"kind\":\"Student\",\"metadata\":{\"annotations\":{},\"name\":\"new-student\",\"namespace\":\"default\"},\"spec\":{\"name\":\"李四\",\"school\":\"深圳小学\"}}\n"}, OwnerReferences:[]v1.OwnerReference(nil), Initializers:(*v1.Initializers)(nil), Finalizers:[]string(nil), ClusterName:"", ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1.StudentSpec{name:"", school:""}} ...
    I0331 23:43:03.790076   21540 controller.go:182] 实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除)
    I0331 23:43:03.790120   21540 controller.go:145] Successfully synced 'default/new-student'
    I0331 23:43:03.790141   21540 event.go:209] Event(v1.ObjectReference{Kind:"Student", Namespace:"default", Name:"new-student", UID:"abcd77d6-53cb-11e9-9d2a-000c29f1f9c9", APIVersion:"bolingcavalry.k8s.io/v1", ResourceVersion:"370653", FieldPath:""}): type: 'Normal' reason: 'Synced' Student synced successfully
    
    1. 接下来您也可以尝试修改和删除已有的Student对象,观察controller控制台的输出,确定是否已经监听到所有student变化的事件,例如删除的事件日志如下:
    I0331 23:44:37.236090   21540 controller.go:171] Student对象被删除,请在这里执行实际的删除业务: default/new-student ...
    I0331 23:44:37.236118   21540 controller.go:145] Successfully synced 'default/new-student'
    

    小结

    至此,controller的编码和验证就全部完成了,现在小结一下自定义controller开发的整个过程:

    1. 创建CRD(Custom Resource Definition),令k8s明白我们自定义的API对象;
    2. 编写代码,将CRD的情况写入对应的代码中,然后通过自动代码生成工具,将controller之外的informer,client等内容较为固定的代码通过工具生成;
    3. 编写controller,在里面判断实际情况是否达到了API对象的声明情况,如果未达到,就要进行实际业务处理,而这也是controller的通用做法;
    4. 实际编码过程并不负载,动手编写的文件如下:
    ├── controller.go
    ├── main.go
    └── pkg
        ├── apis
        │   └── bolingcavalry
        │       ├── register.go
        │       └── v1
        │           ├── doc.go
        │           ├── register.go
        │           └── types.go
        └── signals
            ├── signal.go
            ├── signal_posix.go
            └── signal_windows.go
    

    以上就是k8s自定义controller的整个开发过程,希望在您的开发过程中本文能提供一些参考;

    欢迎关注我的公众号:程序员欣宸

    在这里插入图片描述

    展开全文
  • k8s|ingres-nginx-controller 安装 | 最新版

    千次阅读 2022-03-19 20:50:11
    文章目录前言1、 Ingress和Pod的关系2、 ingress工作流程3、 安装 ingress controller4、测试1、创建nginx pod5、问题1、 nginx ingress contrller EXTERNAL-IP pending2、IngressClass问题原因:解决一:解决二: ...


    ingress 官方网站
    ingress 仓库地址

    前言

    原来我们需要将端口号对外暴露,通过ip+端口号就可以访问

    原来是使用service中的NodePort实现的

    • 每个节点都会启动端口,
    • 在访问的时候通过任何节点,通过ip+端口号就能实现访问

    但是NodePort还存在一些缺陷

    • 因为端口不能重复,所以每个端口只能使用一次,一个端口对应一个应用
    • 实际访问中都是使用域名,根据不同的域名跳转到不同的端口中的

    1、 Ingress和Pod的关系

    Pod和Ingress都是通过service进行关联的,而作为统一入口,由service关联一组pod中

    image-20220319134722078.png

    • 首先service关联我们的pod
    • 然后ingress作为入口,首先需要到service,然后发现一组pod
    • 发现pod后,就可以做负载均衡等操作

    2、 ingress工作流程

    实际访问中,我们都是有域名的,维护很多域名,a.com和b.com

    然后不同的域名对应不同的service,然后service 管理不同的pod

    image-20220319134813598.png

    需要注意ingress不是内置的组件,需要我们单独安装。

    3、 安装 ingress controller

    下面我们通过yaml的方式,部署我们的ingress,配置如下

    # 下载对应的yml
    wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/cloud/deploy.yaml
    
    # 替换镜像地址(国内无法下载)
    sed -i 's@k8s.gcr.io/ingress-nginx/controller:v1.1.1\(.*\)@duangx/ingress-nginx-controller:v1.1.@' deploy.yaml
    sed -i 's@k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1\(.*\)$@duangx/kube-webhook-certgen:v1.1.1@' deploy.yaml
    # 修改yml文件名
    mv  deploy.yaml  ingress-nginx.yaml
    # 部署
    kubectl apply -f ingress-nginx.yaml
    
    # 卸载
    kubectl delete -f ingress-nginx.yaml
    

    4、测试

    1、创建nginx pod

     # 创建命名空间
     kubectl create ns test
     # 在test命名空间创建nginx
     kubectl create deployment  test-ingress --image=nginx -n test
     # 暴漏端口
     kubectl expose deployment test-ingress --port=80 --target-port=80 --type=NodePort  -n test
     
    

    创建ingress服务

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ingress-service
      namespace: test
    spec:
      rules:
      - host: www.ixx.com                    # 自定义的域名
        http:
          paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: test-ingress                               # 上面创建的服务名 svc
                port:
                  number: 80   
    

    5、问题

    1、 nginx ingress contrller EXTERNAL-IP pending

    访问 www.ixx.com 发现访问不通

    查看nginx ingress contrller svc 发现 ingress-nginx-controller 的 EXTERNAL-IP 一直pending

    [root@k8smaster ingress]# kubectl  get svc -n ingress-nginx
    NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
    ingress-nginx-controller             LoadBalancer   10.96.161.205   <pending>     80:30657/TCP,443:30521/TCP   138m
    ingress-nginx-controller-admission   ClusterIP      10.96.178.244   <none>        443/TCP                      138m
    
    # 查看 ingress-nginx-ctroller 节点
    [root@k8smaster ingress]# kubectl  get po -n ingress-nginx -owide
    NAME                                        READY   STATUS      RESTARTS       AGE    IP              NODE       NOMINATED NODE   READINESS GATES
    ingress-nginx-admission-create--1-rp5dt     0/1     Completed   0              153m   10.244.243.1    mcn-test   <none>           <none>
    ingress-nginx-admission-patch--1-w2vrg      0/1     Completed   3 (153m ago)   153m   10.244.206.65   mcn-prod   <none>           <none>
    ingress-nginx-controller-69d84f9c5f-vv9sb   1/1     Running     0              147m   10.244.206.67   mcn-prod   <none>           <none>
    
    # 修改 EXTERNAL-IP为 ingress-nginx-controller(mcn-prod) 的ip(内网))
    kubectl edit -n ingress-nginx service/ingress-nginx-controller
    # 添加   externalIPs: 
    
    

    image-20220319155355699.png

    访问 http://www.ixx.com/ (这个其实不是我们启动的nginx)

    image-20220319155516620.png

    2、IngressClass

    进入容器修改默认页面

    # 进入容器
    kubectl exec -it test-ingress-5c4bb6ff8-kl9ql /bin/bash  -n test
    
    [root@k8smaster ~]# kubectl exec -it test-ingress-5c4bb6ff8-kl9ql /bin/bash  -n test
    kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
    root@test-ingress-5c4bb6ff8-kl9ql:/# echo "test-nginx" >  /usr/share/nginx/html/index.html 
    
    

    再次访问

    image-20220319170018835.png

    查看 ingress-nginx-controller 日志

    [root@k8smaster ~]# kubectl log -f  ingress-nginx-controller-69d84f9c5f-vv9sb -n ingress-nginx
    Error: unknown command "log" for "kubectl"
    
    Did you mean this?
            top
            logs
    
    Run 'kubectl --help' for usage.
    [root@k8smaster ~]# kubectl logs -f  ingress-nginx-controller-69d84f9c5f-vv9sb -n ingress-nginx
    -------------------------------------------------------------------------------
    NGINX Ingress controller
      Release:       v1.1.1
      Build:         a17181e43ec85534a6fea968d95d019c5a4bc8cf
      Repository:    https://github.com/kubernetes/ingress-nginx
      nginx version: nginx/1.19.9
    
    -------------------------------------------------------------------------------
    
    W0319 05:20:50.050104       7 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
    I0319 05:20:50.050214       7 main.go:223] "Creating API client" host="https://10.96.0.1:443"
    I0319 05:20:50.064789       7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.3" state="clean" commit="c92036820499fedefec0f847e2054d824aea6cd1" platform="linux/amd64"
    I0319 05:20:50.226998       7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem"
    I0319 05:20:50.242856       7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key"
    I0319 05:20:50.270217       7 nginx.go:255] "Starting NGINX Ingress controller"
    I0319 05:20:50.277940       7 event.go:282] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"ingress-nginx", Name:"ingress-nginx-controller", UID:"ca616501-792c-4f4b-95a3-453cd05cab36", APIVersion:"v1", ResourceVersion:"8881", FieldPath:""}): type: 'Normal' reason: 'CREATE' ConfigMap ingress-nginx/ingress-nginx-controller
    I0319 05:20:51.472200       7 nginx.go:297] "Starting NGINX process"
    I0319 05:20:51.472265       7 leaderelection.go:248] attempting to acquire leader lease ingress-nginx/ingress-controller-leader...
    I0319 05:20:51.472626       7 nginx.go:317] "Starting validation webhook" address=":8443" certPath="/usr/local/certificates/cert" keyPath="/usr/local/certificates/key"
    I0319 05:20:51.472776       7 controller.go:155] "Configuration changes detected, backend reload required"
    I0319 05:20:51.480203       7 leaderelection.go:258] successfully acquired lease ingress-nginx/ingress-controller-leader
    I0319 05:20:51.480368       7 status.go:84] "New leader elected" identity="ingress-nginx-controller-69d84f9c5f-vv9sb"
    I0319 05:20:51.519115       7 controller.go:172] "Backend successfully reloaded"
    I0319 05:20:51.519175       7 controller.go:183] "Initial sync, sleeping for 1 second"
    I0319 05:20:51.519257       7 event.go:282] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-69d84f9c5f-vv9sb", UID:"84dfa8f2-de4b-4191-872b-8599510919f3", APIVersion:"v1", ResourceVersion:"9612", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
    I0319 06:07:33.859343       7 admission.go:149] processed ingress via admission controller {testedIngressLength:1 testedIngressTime:0.024s renderingIngressLength:1 renderingIngressTime:0s admissionTime:18.0kBs testedConfigurationSize:0.024}
    I0319 06:07:33.859366       7 main.go:101] "successfully validated configuration, accepting" ingress="test/ingress-service"
    I0319 06:07:33.863734       7 store.go:420] "Ignoring ingress because of error while validating ingress class" ingress="test/ingress-service" error="ingress does not contain a valid IngressClass"
    
    
    # 错误  ingress does not contain a valid IngressClass
    
    
    
    

    问题原因:

    ingress does not contain a valid IngressClass

    除了可能会有多个不同类型的 Ingress Controller 之外,还可能存在多个相同类型的 Ingress Controller,比如部署了两个 NGINX Ingress Controller,一个负责处理外网访问,一个负责处理内网访问。

    此时也可以通过上面的方式,为每个 Controller 设定唯一的一个 class。

    当多个 controller 的 class 不唯一,或者 controller 和 Ingress 都没有指定 class 又没有默认的 class 时,会导致所有符合条件的 Ingress Controller 竞争满足 Ingress 配置,可能会导致不可预测的结果。

    解决一:

    创建ingress 添加 IngressClass属性

    查看  IngressClass
    
    [root@k8smaster ingress]# kubectl get ingressClass
    NAME    CONTROLLER             PARAMETERS   AGE
    nginx   k8s.io/ingress-nginx   <none>       3h50m
    
    
    修改我们的service
    
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ingress-service
      namespace: test
    spec:
      # 这一步需要指定ingressClassName,不然信息无法同步到nginx配置中,也会报 ingress does not contain a valid Ingress Class  错误
      ngressClassName: nginx   
      rules:
      - host: www.ixx.com                    # 自定义的域名
        http:
          paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: test-ingress                               # 上面创建的服务名 svc
                port:
                  number: 80   
    

    重新应用

    [root@k8smaster ingress]# vim test-ingress.yml 
    [root@k8smaster ingress]# kubectl apply -f test-ingress.yml 
    ingress.networking.k8s.io/ingress-service configured
    
    

    image-20220319182918980.png

    解决二:

    设置默认的 IngressClass 注意:修改后必须新创建的ingress才会默认使用

    在集群中,我们可以设定一个默认的 Ingress Class,以便处理所有没有指定 Ingress Class 的 Ingress 资源。

    IngressClass 资源上,我们可以通过将 ingressclass.kubernetes.io/is-default-class 注解的值设定为 true,来使没有设置 ingressClassName 的 Ingress 使用此默认的 IngressClass

    注意:当存在多个默认 Ingress Class 时,新的 Ingress 如果没有指定 ingressClassName 则不会被允许创建。解决这个问题只需确保集群中最多只能有一个 IngressClass 被标记为默认。

    image-20220319190115608.png

    image-20220319191132932.png

    展开全文
  • controller层中注入controller

    千次阅读 2020-12-08 15:25:44
    问题描述:在controller中注入了另一个controller,按理说,三个注解controller、service、respository都是component衍生而来,任何被注入容器中的bean都可以以同样方式获取,但是在使用springboot项目时,...
  • Realtek PCIe GBE Family Controller WIN7/WIN2008 驱动 支持32/64位,WIN2008数据中心版亲测通过
  • 搭建SpringMVC模型,包含控制层(@Controller)、 业务逻辑层Service(@Service)、持久层(@Repository)三层注入方式。
  • Kafka Controller工作原理

    千次阅读 2020-12-05 23:52:18
    Controller脑裂 为了解决Controller脑裂问题,ZooKeeper中还有一个与Controller有关的持久节点/controller_epoch,存放的是一个整形值,用于记录Controller发生变更的次数,即记录当前是第几代Controller,也称为...
  • BaseController

    千次阅读 2020-05-29 11:17:55
    在ssm项目中,多个controller会有很多相同的代码或使用相同的功能,例如增删查改,这时我们就可以编写一个BaseController了,然后就继承该类,需要时再使用里面的方法。BaseController代码如下: package ...
  • 文章目录前言Controller... 写恶意ControllerⅣ. 反射获取恶意方法Ⅴ. 向RequestMappingHandlerMapping注册代码运行截图注意事项参考引用完 前言 SpringBoot版本2.4.5 <parent> <groupId>org.springframe
  • 本文翻译自:Difference between ApiController and Controller in ASP.NET MVC I've been playing around with ASP.NET MVC 4 beta and I see two types of controllers now: ApiController and Controller ....
  • 场景: 公司封装了一个日志产品,是以切片(AOP)的形式进行日志记录的,也就是在接口返回值中...其实就是在Controller中注入本Controller自身,然后通过注入的对象调用同一个Controller中的其他接口,有调用别的Contro
  • Pocket Controller-Pro6.0汉化破解版

    热门讨论 2013-06-20 11:36:16
    可以通过PC远程控制WINCE设备的软件,实现类似于QQ的远程协助功能。 同时支持 ActiveSync 和 Wifi 两种方式的连接。 压缩包里的 myPCP.exe 是破解补丁。...不用安装,直接运行 PocketController.exe 即可。
  • Spring Boot(3)之 Controller 参数传递

    万次阅读 2022-01-17 10:07:58
    Controller的参数传递 获取URL中的参数 1、Thymeleaf模板 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text=...
  • Controller层编码规范

    万次阅读 多人点赞 2019-05-28 11:04:14
    1、Controllercontroller层在MVC设计中属于控制层;设计初衷:接受请求并响应请求;所以,该层尽量轻薄,避免编写涉及业务处理的代码。 前后端分离的开发设计模式下,推荐使用@RestController注解,它相当于@...
  • ApplePS2Controller

    热门讨论 2011-11-08 13:59:48
    ApplePS2Controller.kext说明: 苹果系统驱动文件
  • Unity3D 角色控制器CharacterController

    千次下载 热门讨论 2013-08-21 22:09:24
    文章Unity3D学习笔记04:角色控制器CharacterController控制人物移动旋转 的项目源码,免费下载。
  • 详解Spring Boot中Controller用法

    千次阅读 2021-03-06 20:12:48
    ControllerController是SpringBoot里最基本的组件,他的作用是把用户提交来的请求通过对URL的匹配,分配个不同的接收器,再进行处理,然后向用户返回结果。他的重点就在于如何从HTTP请求中获得信息,提取参数,并...
  • Character controller

    千次阅读 2021-12-01 10:51:08
    该blog是本人在金匠学习独立游戏期间上课笔记,欢迎纠正,补充​​​​​​​ ...https://assetstore.unity.com/packages/essentials/starter-assets-first-person-character-controller...
  • 本文涉及的部分名词参看名词解释,此处还要强调两个概念:Controller和Controllee,在本文其实二者是同一个意思,代表XxxController(比如DeploymentController)管理的Xxx(比如Deployment)对象。...
  • Unity中的Character Controller 简介

    千次阅读 2022-04-07 10:48:37
    unity使用Character Controller进行角色控制操作
  • Controller(一)

    千次阅读 2021-06-22 17:56:54
    一、Controller是什么? 顾名思义,Controller就是控制器。它是玩家在游戏中的一个代理对象,也是玩家与游戏中人物或者AI之间的交互的桥梁。它的任务就是掌管人物或AI的所有行为。可以说Controller将人物和AI玩弄...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,196,923
精华内容 478,769
关键字:

controller