> For the complete documentation index, see [llms.txt](https://teamsmiley.gitbook.io/devops/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://teamsmiley.gitbook.io/devops/kubernetes/extension/ingress-nginx.md).

# Ingress nginx

svc를 ingress로 외부에 오픈

## regex

<https://kubernetes.github.io/ingress-nginx/user-guide/ingress-path-matching/>

* priority

  In NGINX, regular expressions follow a first match policy, 그러므로 ingress nginx가 정규식의 길이를 기준으로 역순으로 정렬을 한후 controller에 업데이트합니다.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-1
spec:
  rules:
    - host: test.com
      http:
        paths:
          - path: /foo/bar
            backend:
              serviceName: service1
              servicePort: 80
          - path: /foo/bar/
            backend:
              serviceName: service2
              servicePort: 80
```

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-2
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: test.com
      http:
        paths:
          - path: /foo/bar/(.+)
            backend:
              serviceName: service3
              servicePort: 80
```

이 같은 파일이 두개면 아래와 같이 정렬

```conf
location ~* ^/foo/bar/.+ {
  ...
}

location ~* "^/foo/bar/" {
  ...
}

location ~* "^/foo/bar" {
  ...
}
```

* test.com/foo/bar/1 matches \~\* ^/foo/bar/.+ and will go to service 3.
* test.com/foo/bar/ matches \~\* ^/foo/bar/ and will go to service 2.
* test.com/foo/bar matches \~\* ^/foo/bar and will go to service 1.

테스트를 위해 다음과같은 yaml을 만들어서 적용하엿다.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-1
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: c4.aaaa.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: front
                port:
                  name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-2
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: c4.aaaa.com
      http:
        paths:
          - path: /api(/|$)(.*)/i
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  name: http
```

결과 생성된 파일이다. 결론부터 이야기하면 ingress에 rewrite를 쓰면 그 인그레스에만 적용이 된다. 아래보면 /api부분에만 rewrite $1 가 적용되어 있다.

```conf
## start server c4.aaaa.com
server {
  server_name c4.aaaa.com ;

  listen 80  ;
  listen [::]:80  ;
  listen 443  ssl http2 ;
  listen [::]:443  ssl http2 ;

  set $proxy_upstream_name "-";

  ssl_certificate_by_lua_block {
    certificate.call()
  }

  location ~* "^/api(/|$)(.*)" {

    set $namespace      "default";
    set $ingress_name   "test-ingress-2";
    set $service_name   "api";
    set $service_port   "http";
    set $location_path  "/api(/|${literal_dollar})(.*)";
    set $global_rate_limit_exceeding n;

    rewrite_by_lua_block {
      lua_ingress.rewrite({
        force_ssl_redirect = false,
        ssl_redirect = true,
        force_no_ssl_redirect = false,
        preserve_trailing_slash = false,
        use_port_in_redirects = false,
        global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } }
      })
      balancer.rewrite()
      plugins.run()
    }

    ...

    port_in_redirect off;

    set $balancer_ewma_score -1;
    set $proxy_upstream_name "default-api-http";
    set $proxy_host          $proxy_upstream_name;
    set $pass_access_scheme  $scheme;

    set $pass_server_port    $server_port;

    set $best_http_host      $http_host;
    set $pass_port           $pass_server_port;

    set $proxy_alternative_upstream_name "";

    client_max_body_size                    1m;

    proxy_set_header Host                   $best_http_host;

    # Pass the extracted client certificate to the backend

    # Allow websocket connections
    proxy_set_header                        Upgrade           $http_upgrade;

    proxy_set_header                        Connection        $connection_upgrade;

    proxy_set_header X-Request-ID           $req_id;
    proxy_set_header X-Real-IP              $remote_addr;

    proxy_set_header X-Forwarded-For        $remote_addr;

    proxy_set_header X-Forwarded-Host       $best_http_host;
    proxy_set_header X-Forwarded-Port       $pass_port;
    proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
    proxy_set_header X-Forwarded-Scheme     $pass_access_scheme;

    proxy_set_header X-Scheme               $pass_access_scheme;

    # Pass the original X-Forwarded-For
    proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

    # mitigate HTTPoxy Vulnerability
    # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
    proxy_set_header Proxy                  "";
    # Custom headers to proxied server

    proxy_connect_timeout                   5s;
    proxy_send_timeout                      60s;
    proxy_read_timeout                      60s;

    proxy_buffering                         off;
    proxy_buffer_size                       4k;
    proxy_buffers                           4 4k;

    proxy_max_temp_file_size                1024m;

    proxy_request_buffering                 on;
    proxy_http_version                      1.1;

    proxy_cookie_domain                     off;
    proxy_cookie_path                       off;

    # In case of errors try the next upstream server before returning an error
    proxy_next_upstream                     error timeout;
    proxy_next_upstream_timeout             0;
    proxy_next_upstream_tries               3;

    rewrite "(?i)/api(/|$)(.*)" /$1 break;  # <------------여기
    proxy_pass http://upstream_balancer;

    proxy_redirect                          off;

  }

  location ~* "^/" {

    set $namespace      "default";
    set $ingress_name   "test-ingress-1";
    set $service_name   "front";
    set $service_port   "http";
    set $location_path  "/";
    set $global_rate_limit_exceeding n;

    rewrite_by_lua_block {
      lua_ingress.rewrite({
        force_ssl_redirect = false,
        ssl_redirect = true,
        force_no_ssl_redirect = false,
        preserve_trailing_slash = false,
        use_port_in_redirects = false,
        global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } }
      })
      balancer.rewrite()
      plugins.run()
    }

    port_in_redirect off;

    set $balancer_ewma_score -1;
    set $proxy_upstream_name "default-front-http";
    set $proxy_host          $proxy_upstream_name;
    set $pass_access_scheme  $scheme;

    set $pass_server_port    $server_port;

    set $best_http_host      $http_host;
    set $pass_port           $pass_server_port;

    set $proxy_alternative_upstream_name "";

    client_max_body_size                    1m;

    proxy_set_header Host                   $best_http_host;

    # Pass the extracted client certificate to the backend

    # Allow websocket connections
    proxy_set_header                        Upgrade           $http_upgrade;

    proxy_set_header                        Connection        $connection_upgrade;

    proxy_set_header X-Request-ID           $req_id;
    proxy_set_header X-Real-IP              $remote_addr;

    proxy_set_header X-Forwarded-For        $remote_addr;

    proxy_set_header X-Forwarded-Host       $best_http_host;
    proxy_set_header X-Forwarded-Port       $pass_port;
    proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
    proxy_set_header X-Forwarded-Scheme     $pass_access_scheme;

    proxy_set_header X-Scheme               $pass_access_scheme;

    # Pass the original X-Forwarded-For
    proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

    # mitigate HTTPoxy Vulnerability
    # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
    proxy_set_header Proxy                  "";

    # Custom headers to proxied server

    proxy_connect_timeout                   5s;
    proxy_send_timeout                      60s;
    proxy_read_timeout                      60s;

    proxy_buffering                         off;
    proxy_buffer_size                       4k;
    proxy_buffers                           4 4k;

    proxy_max_temp_file_size                1024m;

    proxy_request_buffering                 on;
    proxy_http_version                      1.1;

    proxy_cookie_domain                     off;
    proxy_cookie_path                       off;

    # In case of errors try the next upstream server before returning an error
    proxy_next_upstream                     error timeout;
    proxy_next_upstream_timeout             0;
    proxy_next_upstream_tries               3;

    proxy_pass http://upstream_balancer;

    proxy_redirect                          off;

  }

}
## end server c4.aaaa.com
```

## 주의사항

아래와 같이 만들면 문제가 잇을수 있다.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-3
  annotations:
    nginx.ingress.kubernetes.io/use-regex: 'true'
spec:
  rules:
    - host: test.com
      http:
        paths:
          - path: /foo/bar/bar
            backend:
              serviceName: test-a
              servicePort: 80
          - path: /foo/bar/[A-Z0-9]{3}
            backend:
              serviceName: test-b
              servicePort: 80
```

생성된 결과는 다음과 같다.

```
location ~* "^/foo/bar/[A-Z0-9]{3}" {
  ...
}
location ~* "^/foo/bar/bar" {
  ...
}
```

test.com/foo/bar/bar 이걸 요청하면 ^/foo/bar/\[A-Z0-9]{3} 여기에 걸려버린다. 원하는대로 안된다.

더 알고 싶으면 다음 링크를 읽어보면된다.

<https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms>

## log - 2022-09-11

로그를 커스터마이즈 하고 싶은 경우가 있다. 관련 도큐먼트는 다음에서 얻을수 있다.

1. <https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/log-format/>
2. <https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/>

log-format은 큰 도움이 안되었다. 그래서 2번을 참고해서 진행햇다.

configmap을 추가하면 되는것인데 helm을 사용하다보니 어디에 추가하는지 샘플이 없어서 고생햇다.

heml차트에 보면 다음 부분이 있다.

```yaml
# Will add custom configuration options to Nginx https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
# config: {} #이부분
config:
  enable-underscores-in-headers: on
  log-format-upstream: '$ingress_name "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id'
```

주석처리후 아래처럼 추가 configmap관련 내용을 추가햇다.

여기서 주의할건 `log-format-upstream`을 수정해야된다.

![](/files/3KG9LJkXiRaX1KDrGTii)

아래 보이는 log-format-stream을 가지고 아무리 해도 적용되지가 않앗다. `log-format-upstream`을 사용하는걸 보고 적용해보니 잘 되었다.

기본값은 다음과 같다.

```conf
$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id
```

나의 경우는 로그에 ingress name을 추가로 넣고 싶엇다. 확인해보니 1번 문서에서 추가로 사용할수 있다고 한다.

![](/files/Z5fO0zNfnmwEQbdMNglI)

그래서 다음과 같이 추가햇다.

```yaml
# Will add custom configuration options to Nginx https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
config:
  enable-underscores-in-headers: on
  log-format-upstream: '$ingress_name "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id'
```

나의 경우는 고객 아이피는 중요한 상황이 아니고 로컬 시간도 중요치 않아서 제거하고 인그레스 이름을 맨 처음에 넣어주고 나머지는 기본값을 사용햇다.

그리고 로그를 확인해보니 다음과 같이 잘 나오는것을 확인햇다.

### loki에서 확인해보기

이제 로그를 로키에서 확인해보자.

```conf
# 기존
{namespace=~"nginx"} | pattern `<remote_addr> - - <time_local> "<method> <_> <_>" <status> <_> <_> "<_>" <_>`  | status="400"

# 수정후
{namespace=~"nginx"} | pattern `<ingress> "<method> <_> <_>" <status> <_> <_> "<_>" <_>` | status=~"[4-5].*" | ingress="IngeressName"
```

이렇게 하면 ingress name별로 필터가 가능해진다.

### grafana dash board

아래처럼 dashboard를 만들어 보앗다.

위에 두 그래프는 프로메테우스에서 메트릭을 가져와서 표현하고 마지막 테이블은 로키에서 로그를 가져와서 뿌려준다.

![](/files/LuPvFPWDkGVU4RXTgRGs)

에러가 발생할때 이제 로그도 함께 볼수가 있어서 좋다.

데시보드 코드는 다음과 같다.

```json
{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": "-- Grafana --",
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": [],
          "type": "dashboard"
        },
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": 35,
  "iteration": 1662994187189,
  "links": [],
  "liveNow": false,
  "panels": [
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "P1809F7CD0C75ACF3"
      },
      "fill": 1,
      "fillGradient": 0,
      "gridPos": {
        "h": 8,
        "w": 24,
        "x": 0,
        "y": 0
      },
      "hiddenSeries": false,
      "id": 6,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": false,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 2,
      "nullPointMode": "connected",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "8.3.3",
      "pointradius": 2,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "P1809F7CD0C75ACF3"
          },
          "exemplar": true,
          "expr": "round(sum(irate(nginx_ingress_controller_requests{ingress=~\"$ingress\"}[2m])) by (ingress), 0.001)",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Ingress Request Volume",
      "tooltip": {
        "shared": true,
        "sort": 2,
        "value_type": "individual"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "$$hashKey": "object:57",
          "format": "reqps",
          "logBase": 1,
          "show": true
        },
        {
          "$$hashKey": "object:58",
          "format": "short",
          "logBase": 1,
          "show": false
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "P1809F7CD0C75ACF3"
      },
      "fill": 1,
      "fillGradient": 0,
      "gridPos": {
        "h": 7,
        "w": 24,
        "x": 0,
        "y": 8
      },
      "hiddenSeries": false,
      "id": 4,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": false,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 2,
      "nullPointMode": "null",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "8.3.3",
      "pointradius": 2,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "P1809F7CD0C75ACF3"
          },
          "exemplar": true,
          "expr": "sum(rate(nginx_ingress_controller_requests{ingress=~\"$ingress\",status!~\"[4-5].*\"}[2m])) by (ingress) / sum(rate(nginx_ingress_controller_requests{ingress=~\"$ingress\"}[2m])) by (ingress)",
          "interval": "10",
          "legendFormat": "{{ingress}}",
          "refId": "A"
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Ingress Success Rate (non-4|5xx responses)",
      "tooltip": {
        "shared": true,
        "sort": 0,
        "value_type": "individual"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "$$hashKey": "object:222",
          "format": "percentunit",
          "logBase": 1,
          "show": true
        },
        {
          "$$hashKey": "object:223",
          "format": "short",
          "logBase": 1,
          "show": false
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "P982945308D3682D1"
      },
      "gridPos": {
        "h": 14,
        "w": 24,
        "x": 0,
        "y": 15
      },
      "id": 2,
      "options": {
        "dedupStrategy": "none",
        "enableLogDetails": true,
        "prettifyLogMessage": false,
        "showCommonLabels": false,
        "showLabels": false,
        "showTime": false,
        "sortOrder": "Descending",
        "wrapLogMessage": false
      },
      "pluginVersion": "8.3.3",
      "targets": [
        {
          "datasource": {
            "type": "loki",
            "uid": "P982945308D3682D1"
          },
          "expr": "{namespace=~\"ingress-nginx|ingress-nginx-internal\"} | pattern `<ingress> \"<method> <_> <_>\" <status> <_> <_> \"<_>\" <_>`| status=~\"[4-5].*\" | ingress=\"$ingress\"",
          "instant": false,
          "range": true,
          "refId": "log"
        }
      ],
      "title": "Http 4-5xx",
      "type": "logs"
    }
  ],
  "schemaVersion": 34,
  "style": "dark",
  "tags": [],
  "templating": {
    "list": [
      {
        "current": {
          "selected": false,
          "text": "shop-ingress",
          "value": "shop-ingress"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "P1809F7CD0C75ACF3"
        },
        "definition": "label_values(ingress) ",
        "hide": 0,
        "includeAll": true,
        "label": "ingress",
        "multi": false,
        "name": "ingress",
        "options": [],
        "query": {
          "query": "label_values(ingress) ",
          "refId": "StandardVariableQuery"
        },
        "refresh": 1,
        "regex": "",
        "skipUrlSync": false,
        "sort": 2,
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-12h",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "",
  "title": "Loggging",
  "uid": "A-3q5wMVz",
  "version": 9,
  "weekStart": ""
}
```

### log-format vs log-format-upstream

아직 잘 모르는데 이 다이어그램이 좀 도움이 되는듯.

![](/files/Ksb2V3kv42lKCDBKhX54)

이걸 한번 잘 읽어봐야겟다. <https://www.nginx.com/blog/logging-upstream-nginx-traffic-cdn77/> 그나저나 ingress-nginx는 왜 upstream로그를 기본으로 사용할가? 프록시니까 맞는것도 같고...

## helmchart 사용중 configmap설정

helmchart를 사용중에 configmap을 설정해야하는 경우가 있다. 매뉴얼등이 전부 configmap만 나오면 다음처럼 하자.

helm chart 에 config에 설정을 넣으면 된다.

```yaml
# Will add custom configuration options to Nginx https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
config:
  enable-underscores-in-headers: on
```

여기에 사용할수 있는 항목들은 다음 페이지에서 확인한다.

<https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#configuration-options>

꽤 많은 옵션들이 여기에서 처리할수 있다.

values 파일을 변경후 argocd에서 로딩을 하거나 적용을 하면 pod에 자동으로 config가 reload가 된다. 중요한것은 pod가 재시작 되지 않는다. 설정만 자동으로 리로드 된다.

만약 설정이 잘못되면 `test fail` 나면서 리로딩 하지 않는다.

## todo

* log-format-upstream 과 log-format의 차이점 파악하기
* dashboard를 kube-prometheus에서 넣어줘야한다.
