Istio

Gyullbb·2024년 10월 18일
0

K8S

목록 보기
11/13

Istio 구성요소

Istio는 크게 네트워크 정책을 설정하고 데이터플레인에 정책을 전달하는 중앙 관리 시스템인 컨트롤플레인, 실제로 서비스 간의 통신을 제어하며 실제 트래픽을 처리하는 데이터플레인으로 이루어진다. 각각에 대해서 자세히 알아본다.

컨트롤 플레인 (Istiod)

Istiod

Istiod는 컨트롤플레인의 관리 시스템이 동작하기 위한 핵심 컴포넌트로 크게는 아래 기능을 담당한다.

  • 서비스 디스커버리 : Kubernetes APIServer로 부터 서비스 정보를 가져옴
  • 트래픽 컨트롤
    • 트래픽 시프팅(Traffic shifting) : 카나리 배포 기능 제공
    • 서킷 브레이커(Circuit Breaker) : 목적지 마이크로서비스에 문제가 있을 경우 접속을 차단하고 출발지 마이크로서비스에 요청 에러를 반환
    • 폴트 인젝션(Fault Injection) : 의도적으로 요청을 지연 / 실패 구현
    • 속도 제한(Rate Limit) : 요청 개수 제한
  • 보안 : TLS 인증성 생성 / 배포 및 서비스 간 인증/인가 관리 수행
  • 모니터링 및 로깅 시스템 연계

위 기능들은 기존에는 Pilot, Gally, Citadel 독립적인 컴포넌트들이 각각 담당을 하여 구현되었으나, Istio 1.5부터 독립적인 컴포넌트들의 기능이 모두 Istiod 단일 프로세스로 통합되었다. 각 독립적인 컴포넌트들이 Istiod 내에서 어떤 식으로 구현되었는지 확인해보자.

코드가 방대하여 Istiod의 전반적인 기능을 이해할 용도로 구현된 기능의 일부만 분석하였다.

Pilot

Pilot은 서비스 디스커버리, 트래픽 컨트롤, Envoy 구성 전달을 하는 기능이다.

서비스 디스커버리

  • Kubernetes 클러스터에서 서비스 정보를 수집
    코드 정보 : istio/pilot/pkg/serviceregistry/kube/controller/controller.go

1) 서비스 디스커버리를 수행할 컨트롤러 생성 및 ServiceEvent를 감지할 이벤트 핸들러 생성

// NewController creates a new Kubernetes controller
// Created by bootstrap and multicluster (see multicluster.Controller).
func NewController(kubeClient kubelib.Client, options Options) *Controller {
	c := &Controller{
		opts:                     options,
		client:                   kubeClient,
		queue:                    queue.NewQueueWithID(1*time.Second, string(options.ClusterID)),
		servicesMap:              make(map[host.Name]*model.Service),
		nodeSelectorsForServices: make(map[host.Name]labels.Instance),
		nodeInfoMap:              make(map[string]kubernetesNode),
		workloadInstancesIndex:   workloadinstances.NewIndex(),
		initialSyncTimedout:      atomic.NewBool(false),

		configCluster: options.ConfigCluster,
	}
  ...
  c.services = kclient.NewFiltered[*v1.Service](kubeClient, kclient.Filter{ObjectFilter: kubeClient.ObjectFilter()})

//이벤트 핸들러 생성
	registerHandlers[*v1.Service](c, c.services, "Services", c.onServiceEvent, nil)

	c.endpoints = newEndpointSliceController(c)
}

2) 서비스에 이벤트가 발생했을 때 함수 실행

  • 서비스가 삭제/추가/업데이트 감지 후 각각에 해당하는 함수를 실행한다.
func (c *Controller) onServiceEvent(pre, curr *v1.Service, event model.Event) error {
	log.Debugf("Handle event %s for service %s in namespace %s", event, curr.Name, curr.Namespace)

	// Create the standard (cluster.local) service.
	svcConv := kube.ConvertService(*curr, c.opts.DomainSuffix, c.Cluster(), c.meshWatcher.Mesh())

	switch event {
    //서비스 삭제 이벤트 시 deleteService 함수 호출
	case model.EventDelete:
		c.deleteService(svcConv)
	default:
    //그 외(생성/업데이트) 이벤트 시 addOrUpdateService 함수 호출
		c.addOrUpdateService(pre, curr, svcConv, event, false)
	}

	return nil
}
  • 서비스 삭제 외 이벤트 발생 시 addOrUpdateService 함수를 실행한다.
    • 최신 서비스 정보를 serviceMap이라는 서비스의 hostname(예: test.default.svc.cluster.local)과 Istio과 관리하는 서비스 객체로 이루어진 Map에 저장한다.
    • 따로 이런 serviceMap이라는 객체를 만든 이유는 Service 의 변화를 빠르게 판단하고, 또 실제 변경 사항이 있을 경우에만 Envoy에 정보를 push 하기 위함이다.
      Service를 확인할 때, 이전에 Service정보와 최신의 Service 정보를 비교하여, 변화가 생겼을 시 XDS를 통해 Envoy에 정보를 push하게 되는데, 이 때, 이전 Service 정보를 모든 Service 리스트를 조회하지 않고도 빠르게 정보를 찾아 최신 Service와 비교를 할 수 있다.
func (c *Controller) addOrUpdateService(pre, curr *v1.Service, currConv *model.Service, event model.Event, updateEDSCache bool) {
...
	c.Lock()
    //prevConv == servicesMap에서 현재 확인하려는 서비스와 동일한 이름을 가진 이전 서비스 상태 
	prevConv := c.servicesMap[currConv.Hostname]
	c.servicesMap[currConv.Hostname] = currConv
	c.Unlock()
  • 기본적으로 needsFullPush는 false인데, 서비스가 외부 IP를 가지고 있을 경우 즉, 서비스가 Gateway로 기능하는 경우 fullpush를 수행하여 모든 서비스와 모든 엔드포인트(EDS)를 업데이트 한다. 이는 클러스터 외부에서 접근 가능한 서비스에 변경사항이 생길 경우, 트래픽의 라우팅 규칙이 변경될 수 있기 때문이다.
  • NodePort 서비스의 경우에도 fullpush가 일어나는데, 이는 NodePort 서비스가 변경 될 경우 모든 노드의 규칙에 영향을 끼치기 때문이다.
  • 그 외의 서비스의 경우는 변경된 서비스에 관련해서만 업데이트가 발생한다.
...
//서비스가 Gateway로 기능하는 경우 모든 EDS 업데이트
		needsFullPush = c.extractGatewaysFromService(currConv)
...
//서비스가 NodePort일 경우 모든 EDS 업데이트
		needsFullPush = c.updateServiceNodePortAddresses(currConv)
...
  // This full push needed to update ALL ends endpoints, even though we do a full push on service add/update
	// as that full push is only triggered for the specific service.
	if needsFullPush {
		// networks are different, we need to update all eds endpoints
		c.opts.XDSUpdater.ConfigUpdate(&model.PushRequest{Full: true, Reason: model.NewReasonStats(model.NetworksTrigger)})
	}
  • 신규로 변환한 Service의 Endpoint정보를 수집하여 Envoy가 사용하는 cache에 업데이트 한다.
  // We also need to update when the Service changes. For Kubernetes, a service change will result in Endpoint updates,
	// but workload entries will also need to be updated.
	// TODO(nmittler): Build different sets of endpoints for cluster.local and clusterset.local.
	if updateEDSCache || features.EnableK8SServiceSelectWorkloadEntries {
		endpoints := c.buildEndpointsForService(currConv, updateEDSCache)
		if len(endpoints) > 0 {
			c.opts.XDSUpdater.EDSCacheUpdate(shard, string(currConv.Hostname), ns, endpoints)
		}
	}
  • prevConv와 currConv를 비교해서 변화가 없을 경우에 Envoy에 Push를 하지 않는다.
    변화가 있을 경우에는 XDS를 통해 Envoy에 Service 정보를 업데이트를 알리고, NotifyServiceHandler를 호출하여 서비스 변경 이벤트를 알린다.
	// filter out same service event
   	//prevConv와 currConv가 동일할 경우 Envoy에 Push하지 않음.
	if event == model.EventUpdate && !serviceUpdateNeedsPush(pre, curr, prevConv, currConv) {
		return
	}

	c.opts.XDSUpdater.SvcUpdate(shard, string(currConv.Hostname), ns, event)
	c.handlers.NotifyServiceHandlers(prevConv, currConv, event)
}

트래픽 컨트롤
변경된 엔드포인트를 캐시하고, VirtualService 및 DestinationRule에 따라 라우팅 정보를 재생성한다.

VirtualService를 통해 Traffic shifting, Fault Injection을 구현할 수 있으며, DestinationRule을 통해 Circuit Breaker를 설정하고,
EnvoyFilter를 통해 Rate Limit을 설정할 수 있다.

대표적으로 VirtualService를 처리하는 코드를 확인해본다.

1) VirtualService 처리 - 라우팅 규칙 생성

  • VirtualService의 Spec에서 http 라우트 규칙을 추출한 후, match 즉, uri 경로가 명시된 경우와, match 설정이 없는 경우를 나누어서 라우팅 규칙 slice를 생성한다.
//istio/pilot/pkg/networking/core/route/route.go
func BuildHTTPRoutesForVirtualService(
	node *model.Proxy,
	virtualService config.Config,
	serviceRegistry map[host.Name]*model.Service,
	hashByDestination DestinationHashMap,
	listenPort int,
	gatewayNames sets.String,
	opts RouteOptions,
) ([]*route.Route, error) {
	vs, ok := virtualService.Spec.(*networking.VirtualService)
...

	out := make([]*route.Route, 0, len(vs.Http))

	catchall := false
	for _, http := range vs.Http {
    //match가 0일 경우 라우팅 규칙 생성
		if len(http.Match) == 0 {
			if r := translateRoute(node, http, nil, listenPort, virtualService, serviceRegistry,
				hashByDestination, gatewayNames, opts); r != nil {
				out = append(out, r)
			}
			catchall = true
		} else {
        //match설정이 있을 경우 라우팅 규칙 생성
			for _, match := range http.Match {
				if r := translateRoute(node, http, match, listenPort, virtualService, serviceRegistry,
					hashByDestination, gatewayNames, opts); r != nil {
					out = append(out, r)
...
	return out, nil
}

2) RDS Push가 필요할 경우 BuildHTTPRoute를 통해 라우팅 규칙을 생성하고, 생성된 규칙을 xDS를 통해 Envoy에 전달한다.

//istio/pilot/pkg/xds/xdsgen.go
func (s *DiscoveryServer) pushXds(con *Connection, w *model.WatchedResource, req *model.PushRequest) error {
...
	res, logdata, err := gen.Generate(con.proxy, w, req)
...
}

//istio/pilot/pkg/xds/rds.go
func (c RdsGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
...
//라우팅 규칙을 생성하는 BuildHTTPRoutes 함수 호출
	resources, logDetails := c.ConfigGenerator.BuildHTTPRoutes(proxy, req, w.ResourceNames)
	return resources, logDetails, nil
}

//istio/pilot/pkg/networking/core/httproute.go
func (configgen *ConfigGeneratorImpl) BuildHTTPRoutes(
	node *model.Proxy,
	req *model.PushRequest,
	routeNames []string,
) ([]*discovery.Resource, model.XdsLogDetails) {
...
// 함수를 타고 들어가다 보면 위에서 언급한 BuildHTTPRoutesForVirtualService 함수 등을 호출하여 라우팅 규칙을 생성함.
}

Envoy 구성 전달
istiod는 xDS Sync API를 통해 Envoy에 수집한 구성을 전달한다.

  • LDS(Listener Discovery Service)
  • RDS(Route Discovery Service)
  • CDS(Cluster Discovery Service)
  • EDS(Endpoint Discovery Service)
    아래 구성들을 ADS(Aggregated Discovery Service)라는 단일 요청으로 xDS API를 통해 Envoy에 전달하게 된다.

코드 정보 : istio/pilot/pkg/xds/ads.go
코드 정보 : istio/pkg/xds/server.go
코드 정보 : istio/pkg/xds/discovery.go

1) Envoy와의 연결 수립

  • Envoy와의 연결 수립에 앞서 gRPC Context와 클라이언트 정보를 초기화 한다.
//istio/pilot/pkg/xds/ads.go
// StreamAggregatedResources implements the ADS interface.
func (s *DiscoveryServer) StreamAggregatedResources(stream DiscoveryStream) error {
	return s.Stream(stream)
}

func (s *DiscoveryServer) Stream(stream DiscoveryStream) error {
...
	ctx := stream.Context()
	peerAddr := "0.0.0.0"
	if peerInfo, ok := peer.FromContext(ctx); ok {
		peerAddr = peerInfo.Addr.String()
	}
  • 클라이언트의 인증을 확인하고, 인증에 성공하면 인증된 xDS요청이라는 로그와 함께 ID 정보를 반환한다.
//istio/pilot/pkg/xds/ads.go
	ids, err := s.authenticate(ctx)
	if err != nil {
		return status.Error(codes.Unauthenticated, err.Error())
	}
	if ids != nil {
		log.Debugf("Authenticated XDS: %v with identity %v", peerAddr, ids)
	} else {
		log.Debugf("Unauthenticated XDS: %s", peerAddr)
	}
  • Envoy로 전달할 xDS 리소스를 초기화하고, 클라이언트(envoy)와의 연결 객체를 생성한다.
    • 연결 객체를 생성한 후 xds.Stream을 통해 Envoy와의 xDS스트림을 시작한다.
//istio/pilot/pkg/xds/ads.go
	// InitContext returns immediately if the context was already initialized.
	if err = s.globalPushContext().InitContext(s.Env, nil, nil); err != nil {
		// Error accessing the data - log and close, maybe a different pilot replica
		// has more luck
		log.Warnf("Error reading config %v", err)
		return status.Error(codes.Unavailable, "error reading config")
	}
	con := newConnection(peerAddr, stream)
	con.ids = ids
	con.s = s
	return xds.Stream(con)
}

3) LDS, RDS, CDS, EDS 관련 요청이 있을 경우 ConfigUpdate 함수를 통해 pushChannel에 request를 넣는다.
대표적 예시로 EDS함수를 확인해보자.

//EDS
//istio/pilot/pkg/xds/eds.go
func (s *DiscoveryServer) EDSUpdate(shard model.ShardKey, serviceName string, namespace string,
	istioEndpoints []*model.IstioEndpoint,
) {
	inboundEDSUpdates.Increment()
	// Update the endpoint shards
	pushType := s.Env.EndpointIndex.UpdateServiceEndpoints(shard, serviceName, namespace, istioEndpoints)
	if pushType == model.IncrementalPush || pushType == model.FullPush {
		// Trigger a push
        //EDSUpdate가 되었을 경우 ConfigUpdate 함수 호출
		s.ConfigUpdate(&model.PushRequest{
			Full:           pushType == model.FullPush,
			ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: serviceName, Namespace: namespace}),
			Reason:         model.NewReasonStats(model.EndpointUpdate),
		})
	}
}

//istio/pkg/xds/discovery.go
func (s *DiscoveryServer) ConfigUpdate(req *model.PushRequest) {
...
//요청을 pushChannel에 넣음.
	s.pushChannel <- req
}

4) Istiod에서 Envoy에 Push할 이벤트가 있는 경우(pushChannel) 시작된 xDS 스트림에서 해당 이벤트 및 구성(xDS)을 전달한다.

//istio/pkg/xds/server.go
func Stream(ctx ConnectionContext) error {
...
		select {
...
//앞에서 EDS 변화가 발생하여 생긴 요청을 pushChannel에 넣었음. 해당 요청을 Push함수를 통해 최종적으로 xDS로 Envoy에 전달함.
		case pushEv := <-con.pushChannel:
			err := ctx.Push(pushEv)
			if err != nil {
				return err
			}
		case <-con.stop:
			return nil
}

//istio/pkg/xds/ads.go
func (conn *Connection) Push(ev any) error {
	pushEv := ev.(*Event)
	err := conn.s.pushConnection(conn, pushEv)
	pushEv.done()
	return err
}

func (s *DiscoveryServer) pushConnection(con *Connection, pushEv *Event) error {
	pushRequest := pushEv.pushRequest
...
	wrl := con.watchedResourcesByOrder()
	for _, w := range wrl {
		if err := s.pushXds(con, w, pushRequest); err != nil {
			return err
		}
	}
...
}

Galley

Galley는 Istio의 구성 데이터를 검증하고 변환하는 기능을 제공하는 기능이다.

Istiod에서는 해당 기능을 아래 코드에서 제공하고 있다. 대표적으로 HTTPHeaderName을 검증하는 코드를 확인해본다.

1) HTTP header name이 비어있거나, validHeaderRegex에 해당하지 않는 name이라면 error를 반환한다.

//istio/pkg/config/validation

var validHeaderRegex = regexp.MustCompile("^[-_A-Za-z0-9]+$")
// ValidateHTTPHeaderName validates a header name
func ValidateHTTPHeaderName(name string) error {
	if name == "" {
		return fmt.Errorf("header name cannot be empty")
	}
	if !validHeaderRegex.MatchString(name) {
		return fmt.Errorf("header name %s is not a valid header name", name)
	}
	return nil
}

이 외에도 Metadata, Wight, Percent, Gateway, Server, Port 등 다양한 항목들에 대한 검증 기능을 제공한다.

Citadel

Citadel은 Istio의 보안 관리 및 인증을 제공하는 기능이다.

Istiod에서는 해당 기능을 아래 코드에서 제공하고 있다. 대표적으로 자체 서명 CA 인증서를 주기적으로 생성/갱신하고, 이를 Kubernetes Secret으로 저장하는 코드를 확인해본다.

1) 기존 CA인증서를 로드하고, 기존 인증서가 없을 경우, 새로운 인증서를 생성한다. 이 후 인증서를 Kubernetes Secret으로 저장한다.

//isito/security/pkg/pki/ca/ca.go
func NewSelfSignedIstioCAOptions(ctx context.Context,
	rootCertGracePeriodPercentile int, caCertTTL, rootCertCheckInverval, defaultCertTTL,
	maxCertTTL time.Duration, org string, useCacertsSecretName, dualUse bool, namespace string, client corev1.CoreV1Interface,
	rootCertFile string, enableJitter bool, caRSAKeySize int,
) (caOpts *IstioCAOptions, err error) {
...
//기존 인증서 로드
		err := loadSelfSignedCaSecret(client, namespace, caCertName, rootCertFile, caOpts)
    ...
			pkiCaLog.Infof("CASecret %s not found, will create one", caCertName)
      ...
//없을 경우 새로운 인증서 생성
			pemCert, pemKey, ckErr := util.GenCertKeyFromOptions(options)
      ...
			if caOpts.KeyCertBundle, err = util.NewVerifiedKeyCertBundleFromPem(pemCert, pemKey, nil, rootCerts); err != nil {
				pkiCaLog.Warnf("failed to create CA KeyCertBundle (%v)", err)
				return fmt.Errorf("failed to create CA KeyCertBundle (%v)", err)
			}
...
//생성한 인증서로 Kubernetes Secret 생성
			secret := BuildSecret(caCertName, namespace, nil, nil, pemCert, pemCert, pemKey, istioCASecretType)
			_, err = client.Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
}

데이터 플레인 (envoy proxy)

Envoy Proxy는 각 애플리케이션에 사이드카로 배포되어 서비스 간의 통신을 제어하고 처리한다. 앞서 살펴본 Istiod로 부터 설정을 받아와 동작하며, Istiod와 Envoy는 xDS프로토콜을 사용해 통신한다.

자세한 기능은 Envoy 설명에서 확인해본다.

Envoy

Envoy에는 아래와 같은 구성요소들이 있다.

  • Listener: Envoy가 들어오는 트래픽을 수신하는 방법(예: IP/포트)을 정의함
  • Route: 들어오는 요청을 규칙(예: 헤더, 경로)에 따라 특정 클러스터에 매핑함
  • Cluster: 업스트림 엔드포인트(서비스 인스턴스)를 논리적으로 그룹화한 것
  • Endpoint: 클러스터 내의 특정 백엔드 인스턴스(IP/포트)
  • Filter: 인증, 속도 제한 등 다양한 단계에서 트래픽을 처리함
  • Upstream: Envoy가 요청을 전달하는 대상 서비스나 서버
  • Downstream: Envoy에 요청을 시작하는 클라이언트나 서비스

Envoy proxy 실습

envoy를 설치하고 간단한 envoy proxy 실습을 진행한다.

# wget -O- https://apt.envoyproxy.io/signing.key | sudo gpg --dearmor -o /etc/apt/keyrings/envoy-keyring.gpg

# echo "deb [signed-by=/etc/apt/keyrings/envoy-keyring.gpg] https://apt.envoyproxy.io jammy main" | sudo tee /etc/apt/sources.list.d/envoy.list

# sudo apt-get update && sudo apt-get install envoy -y

# envoy --version
envoy  version: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9/1.32.0/Clean/RELEASE/BoringSSL

static_resources:

  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite_literal: www.envoyproxy.io
                  cluster: service_envoyproxy_io

  clusters:
  - name: service_envoyproxy_io
    type: LOGICAL_DNS
    # Comment out the following line to test on v6 networks
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_envoyproxy_io
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.envoyproxy.io
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: www.envoyproxy.io

통신 확인

root@testpc:~# envoy -c envoy-demo.yaml

root@testpc:~# ss -tnlp
State      Recv-Q     Send-Q          Local Address:Port            Peer Address:Port     Process
LISTEN     0          4096                  0.0.0.0:10000                0.0.0.0:*         users:(("envoy",pid=3407,fd=25))

root@testpc:~# curl -s http://127.0.0.1:10000 | grep -o "<title>.*</title>"
<title>Envoy proxy - home</title>

(|default:N/A) root@k3s-s:~# curl -s http://192.168.10.200:10000 | grep -o "<title>.*</title>"
<title>Envoy proxy - home</title>

root@testpc:~# 
...
[2024-10-18T23:24:30.719Z] "GET / HTTP/1.1" 200 - 0 15795 355 284 "-" "curl/7.81.0" "6af88bbd-742c-4eb2-be25-af8c364b23c0" "www.envoyproxy.io" "46.137.195.11:443"

관리자 페이지 설정 덮어쓰기

cat <<EOT> envoy-override.yaml
admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9902
EOT
envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)"

# envoy 관리페이지 외부 접속 정보 출력
echo -e "http://$(curl -s ipinfo.io/ip):9902"

Istio 설치

아래 yaml을 배포하여 Istio Base Component와 Istiod를 설치한다.
복잡성을 줄이기 위해 ingressgateway는 활성화하고 egressgateway는 비활성화 한다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  components:
    base:
      enabled: true
    egressGateways:
    - enabled: true
      name: istio-egressgateway
    ingressGateways:
    - enabled: true
      name: istio-ingressgateway
    pilot:
      enabled: true
  hub: docker.io/istio
  profile: demo
  tag: 1.23.2
  values:
    defaultRevision: ""
    gateways:
      istio-egressgateway: {}
      istio-ingressgateway: {}
    global:
      configValidation: true
      istioNamespace: istio-system
    profile: demo

istio를 배포하게 되면 아래와 같은 리소스들이 생성된다.
*istio-ingressgateway는 NodePort로 변경함.

Service

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   NodePort    10.10.200.37    <none>        15021:32488/TCP,80:32102/TCP,443:31354/TCP,31400:32683/TCP,15443:30575/TCP   17m
istiod                 ClusterIP   10.10.200.238   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP

pod

NAME                                        READY   STATUS    RESTARTS   AGE
pod/istio-ingressgateway-5f9f654d46-w87wh   1/1     Running   0          4m35s
pod/istiod-7f8b586864-jl9fd                 1/1     Running   0          4m51s

crd

#kubectl get crd | grep istio.io | sort
NAME                                              MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/istio-ingressgateway   1               N/A               0                     4m35s
poddisruptionbudget.policy/istiod                 1               N/A               0                     4m51s
authorizationpolicies.security.istio.io    2024-10-19T15:15:33Z
destinationrules.networking.istio.io       2024-10-19T15:15:33Z
envoyfilters.networking.istio.io           2024-10-19T15:15:34Z
gateways.networking.istio.io               2024-10-19T15:15:34Z
peerauthentications.security.istio.io      2024-10-19T15:15:34Z
proxyconfigs.networking.istio.io           2024-10-19T15:15:34Z
requestauthentications.security.istio.io   2024-10-19T15:15:34Z
serviceentries.networking.istio.io         2024-10-19T15:15:34Z
sidecars.networking.istio.io               2024-10-19T15:15:34Z
telemetries.telemetry.istio.io             2024-10-19T15:15:34Z
virtualservices.networking.istio.io        2024-10-19T15:15:34Z
wasmplugins.extensions.istio.io            2024-10-19T15:15:34Z
workloadentries.networking.istio.io        2024-10-19T15:15:34Z
workloadgroups.networking.istio.io         2024-10-19T15:15:34Z
이름설명
poddisruptionbudget.policy/istio-ingressgatewayIstio Ingress Gateway의 안정성을 보장하기 위해 최소 가용 Pod 수를 설정한다.
poddisruptionbudget.policy/istiodIstio Control Plane(istiod)이 일정 수 이상의 Pod을 유지하도록 설정한다.
authorizationpolicies.security.istio.io서비스 간 통신 및 외부 요청에 대한 접근 제어 규칙을 정의한다.
destinationrules.networking.istio.io서비스 호출 시 적용할 로드 밸런싱, 연결 설정 등의 정책을 정의한다.
envoyfilters.networking.istio.ioEnvoy 프록시의 동작을 커스터마이징하기 위해 필터를 추가한다.
gateways.networking.istio.io외부 트래픽을 내부 서비스로 라우팅하기 위한 Gateway를 정의한다.
peerauthentications.security.istio.io서비스 간 통신에 대한 인증 정책을 설정한다. (mTLS 등)
proxyconfigs.networking.istio.io프록시 설정을 제어하며, 사이드카 및 인그레스 동작을 커스터마이징한다.
requestauthentications.security.istio.io들어오는 요청에 대한 인증을 처리하는 규칙을 정의한다.
serviceentries.networking.istio.io외부 서비스에 대한 접근을 허용하기 위해 내부에 가상 서비스 항목을 만든다.
sidecars.networking.istio.io특정 네임스페이스나 서비스에 대해 사이드카 프록시 동작을 정의한다.
telemetries.telemetry.istio.io모니터링 및 메트릭 수집을 위한 텔레메트리 설정을 정의한다.
virtualservices.networking.istio.io요청을 특정 서비스로 라우팅하기 위한 규칙을 정의한다.
wasmplugins.extensions.istio.ioWebAssembly(WasM) 플러그인을 사용하여 Istio 프록시를 확장한다.
workloadentries.networking.istio.io클러스터 외부의 워크로드를 Istio 메쉬에 포함시킨니다.
workloadgroups.networking.istio.io비슷한 특성을 가진 외부 워크로드를 그룹으로 정의한다.

istio-ingressgateway

istio-ingressgateway는 Istio의 외부 트래픽을 내부 서비스로 라우팅 하는 역할이다. deployment에서 ps -ef를 확인하면 다음과 같다.

  • pilot-agent : Envoy proxy가 정상 작동하도록 사이드카 컨테이너를 관리한다.
  • envoy : 트래픽을 규칙에 따라 처리하고 요청을 라우팅 한다.
(|default:N/A) root@k3s-s:~# kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
istio-p+       1       0  0 15:16 ?        00:00:00 /usr/local/bin/pilot-agent proxy router --domain istio-system.svc.cluster.local -
istio-p+      12       1  0 15:16 ?        00:00:02 /usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-
istio-p+      61       0  0 15:36 pts/0    00:00:00 ps -ef

envoy 프로세스에서 사용하는 envoy 구성 파일을 확인해본다.

(|default:N/A) root@k3s-s:~# kubectl exec -it deployment.apps/istio-ingressgateway -n istio-system -- cat /etc/istio/proxy/envoy-rev.json
구성 요소설명주요 값/설정
application_log_config애플리케이션 로그 포맷을 정의%Y-%m-%dT%T.%fZ\t%l\tenvoy %n %g:%#\t%v\tthread=%t
node 정보Envoy 노드 및 메타데이터 관련 정보id: router~172.16.1.3~...
cluster: istio-ingressgateway.istio-system
instance_ip: 172.16.1.3
annotationsIstio 및 Kubernetes 메타데이터 정보istio.io/rev: default, prometheus.io/scrape: true
layered_runtimeEnvoy 런타임 설정을 정의- overload.global_downstream_max_connections: 2147483647
- re2.max_program_size.error_level: 32768
bootstrap_extensions내부 리스너 구성.buffer_size_kb: 64
admin 설정관리 인터페이스 관련 설정address: 127.0.0.1:15000
profile_path: /var/lib/istio/data/envoy.prof
dynamic_resources동적 리소스 관리 설정(LDS, CDS, ADS)api_type: DELTA_GRPC
discovery_address: istiod.istio-system.svc:15012
static_resources정적 리소스(클러스터, 리스너) 설정클러스터: prometheus_stats, agent, xds-grpc
리스너: 0.0.0.0:15090, 0.0.0.0:15021
클러스터 설정xDS, Prometheus 등과의 연결을 위한 클러스터 정보Circuit Breakers: Max 100,000 connections/requests
리스너 설정네트워크 리스너 구성리스너 포트: 15090, 15021
HTTP 필터: Router, Health Check
proxy_config프록시 동작을 위한 세부 설정binaryPath: /usr/local/bin/envoy
concurrency: 2
statusPort: 15020
메타데이터Pod 및 서비스와 관련된 정보.Pod 이름: istio-ingressgateway-5f9f654d46-w87wh
서비스 계정: istio-ingressgateway-service-account

istio 접속 테스트

현재는 istio ingress gateway 외부 노출 설정이 없기 때문에 nodePort를 기반으로 접속을 시도하였을 때 접속 실패가 된다.

(|default:N/A) root@k3s-s:~# export IGWHTTP=$(kubectl get service -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[1].nodePort}')
echo $IGWHTTP
32102

(|default:N/A) root@k3s-s:~# export MYDOMAIN=www.gyuri.dev

(|default:N/A) root@k3s-s:~# echo -e "192.168.10.10 $MYDOMAIN" >> /etc/hosts
echo -e "export MYDOMAIN=$MYDOMAIN" >> /etc/profile

(|default:N/A) root@k3s-s:~# curl -v -s $MYDOMAIN:$IGWHTTP
*   Trying 192.168.10.10:32102...
* connect to 192.168.10.10 port 32102 failed: Connection refused
* Failed to connect to www.gyuri.dev port 32102 after 1 ms: Connection refused
* Closing connection 0

Istio 외부 노출


nginx Deployment 배포
nginx Deployment를 배포한다. 이 때, default namespace에 istio-injection=enabled설정을 통해 해당 네임스페이스에 배포된 파드들에 istio사이드카가 붙도록 설정한다.

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kans-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-websrv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      serviceAccountName: kans-nginx
      terminationGracePeriodSeconds: 0
      containers:
      - name: deploy-websrv
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: svc-clusterip
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 80
  selector:
    app: deploy-websrv
  type: ClusterIP
EOF
(|default:N/A) root@k3s-s:~# kubectl label namespace default istio-injection=enabled
namespace/default labeled

(|default:N/A) root@k3s-s:~# kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
pod/deploy-websrv-778ffd6947-zdgdf   2/2     Running   0          15s   172.16.1.4   k3s-w1   <none>           <none>

Gateway, VirtualService 배포
지정한 ingress gateway로부터 인입된 트래픽을 관리하기 위해 Gateway를 배포하고, 인입 처리할 hosts 설정 및 목적지 라우팅 정책을 설정하기 위해 VirtualService를 배포한다.

apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: test-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: nginx-service
spec:
  hosts:
  - "$MYDOMAIN"
  gateways:
  - test-gateway
  http:
  - route:
    - destination:
        host: svc-clusterip
        port:
          number: 80
EOF

istio proxy들이 정상적으로 작동하는지 점검한다.

(|default:N/A) root@k3s-s:~# kubectl get gw,vs
NAME                                       AGE
gateway.networking.istio.io/test-gateway   2s

NAME                                               GATEWAYS           HOSTS               AGE
virtualservice.networking.istio.io/nginx-service   ["test-gateway"]   ["www.gyuri.dev"]   2s

(|default:N/A) root@k3s-s:~# istioctl proxy-status
NAME                                                   CLUSTER        CDS                LDS                EDS                RDS                ECDS        ISTIOD                      VERSION
deploy-websrv-778ffd6947-zdgdf.default                 Kubernetes     SYNCED (4m18s)     SYNCED (4m18s)     SYNCED (4m18s)     SYNCED (4m18s)     IGNORED     istiod-7f8b586864-jl9fd     1.23.2
istio-ingressgateway-5f9f654d46-w87wh.istio-system     Kubernetes     SYNCED (46s)       SYNCED (46s)       SYNCED (4m18s)     SYNCED (46s)       IGNORED     istiod-7f8b586864-jl9fd     1.23.2

접속 테스트

(|default:N/A) root@k3s-s:~# curl -s $MYDOMAIN:$IGWHTTP | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>
(|default:N/A) root@k3s-s:~# kubetail -n istio-system -l app=istio-ingressgateway -f
Will tail 1 logs...
istio-ingressgateway-5f9f654d46-w87wh
[istio-ingressgateway-5f9f654d46-w87wh] [2024-10-19T16:11:05.545Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 8 8 "172.16.0.0" "curl/7.81.0" "2ee7d856-eb06-94a5-8a08-d2f6ab77c9fd" "www.gyuri.dev:32102" "172.16.1.4:80" outbound|80||svc-clusterip.default.svc.cluster.local 172.16.1.3:33970 172.16.1.3:8080 172.16.0.0:52692 - -
[istio-ingressgateway-5f9f654d46-w87wh] [2024-10-19T16:11:14.050Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 2 1 "172.16.0.0" "curl/7.81.0" "7eff26f0-f971-96c1-8365-9de15f2cfac0" "www.gyuri.dev:32102" "172.16.1.4:80" outbound|80||svc-clusterip.default.svc.cluster.local 172.16.1.3:33970 172.16.1.3:8080 172.16.0.0:4801 - -

0개의 댓글