티스토리 뷰

문제 현상

ONOS 컨테이너에서, 서로 다른 컨테이너가 관리하는 device 사이의 link가 없는 것으로 나타나고, 주기적으로 LLDP Packet failed to validate!라는 warn 수준의 로그가 발생한다.


문제 현상 재현

Ubuntu에서 Docker로 ONOS 컨테이너를 2개 실행한다.

$ sudo docker run -it --rm --name onos1 onosproject/onos:2.2.0
$ sudo docker run -it --rm --name onos2 onosproject/onos:2.2.0

두 ONOS에서 org.onosproject.openflow 애플리케이션을 활성화한다.

$ onos-app 172.17.0.2 activate org.onosproject.openflow
$ onos-app 172.17.0.3 activate org.onosproject.openflow

Mininet으로 다음과 같은 구조의 네트워크를 구성한다.

컨트롤러 2개(Remote Controller; IP 172.17.0.2, 172.17.0.3), 컨트롤러마다 스위치 3개씩 연결, 같은 컨트롤러와 연결되는 스위치끼리 상호 연결, 서로 다른 컨트롤러가 관리하는 스위치 한 쌍을 상호 연결, Mininet CLI 사용, OpenFlow 1.3 사용

그러면 ONOS 로그에는 LLDP Packet failed to validate!라는 내용으로 warn 수준의 로그가 계속 발생하는 것을 볼 수 있다.

10:46:54.997 WARN  [LinkDiscovery] LLDP Packet failed to validate!

ONOS CLI에서 devices, links 명령어로 device와 link 목록을 확인해 보면, Device는 각각 3개씩 정상적으로 잡혀 있지만, link는 DIRECT type 6개와 EDGE type 1개가 아니라 DIRECT type 6개만 잡혀 있다.

onos@root > devices
id=of:0000000000000001, available=true, local-status=connected 41s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=1, driver=ovs, channelId=172.17.0.1:54560, managementAddress=172.17.0.1, protocol=OF_13
id=of:0000000000000002, available=true, local-status=connected 41s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=2, driver=ovs, channelId=172.17.0.1:54554, managementAddress=172.17.0.1, protocol=OF_13
id=of:0000000000000003, available=true, local-status=connected 41s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=3, driver=ovs, channelId=172.17.0.1:54562, managementAddress=172.17.0.1, protocol=OF_13
onos@root > links
src=of:0000000000000001/1, dst=of:0000000000000003/1, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000001/2, dst=of:0000000000000002/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000002/1, dst=of:0000000000000003/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000002/2, dst=of:0000000000000001/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000003/1, dst=of:0000000000000001/1, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000003/2, dst=of:0000000000000002/1, type=DIRECT, state=ACTIVE, expected=false

해결 방법

일반적인 Ubuntu에서 ONOS를 구동할 때는 cluster 정보가 무작위로 생성되기 때문에 문제가 생기지 않는다. 그러나 Docker로 ONOS 컨테이너를 돌릴 때는 무작위의 cluster 정보를 자동으로 생성하지 않기 때문에 cluster 이름이 모두 default로 중복되어서 위의 문제 현상이 발생한다. 따라서 cluster 정보만 넣어 주면 이 문제를 해결할 수 있다. 여기서는 별도의 cluster 정보 파일을 넣은 디렉터리를 컨테이너에 마운트하여 해결한다. (이 해결 방법 예제에서는 여러 ONOS를 cluster로 묶지는 않았다.)

Cluster 관련 설정 파일 구성

실행할 컨테이너 개수에 맞춰서 디렉터리를 생성한다. 이 예제에서는 ONOS 컨테이너 2개를 돌릴 것이므로, ~/onos-config 디렉터리 아래에 2개의 디렉터리 onos1, onos2를 만들었다. 각 디렉터리 안에 들어갈 cluster.json까지 작성하면, ~/onos-config 디렉터리의 구조는 다음과 같다.

onos-config
├── onos1
│   └── cluster.json
└── onos2
    └── cluster.json

각 디렉터리에서 cluster.json 파일을 다음과 같은 형태로 작성한다. 실행할 컨테이너에 맞게 각각 ipid를 지정한다. name은 컨테이너의 이름과 달라도 상관없고, 서로 중복되지만 않으면 된다. port는 cluster 내부에서 서로 통신하기 위한 포트 번호로 약속된 9876으로 지정한다.

{
  "name": "onos1",
  "node": {
    "ip": "172.17.0.2",
    "id": "172.17.0.2",
    "port": 9876
  }
}

컨테이너 구동 및 확인

다음과 같이 -v <host-path>:<container-path> 옵션을 주어서, 방금 만든 cluster.json을 포함하는 디렉터리를 컨테이너의 /root/onos/config 디렉터리에 마운트하여 컨테이너를 실행한다. 컨테이너의 이름은 앞서 만든 cluster.json 파일의 name과 달라도 괜찮다.

$ sudo docker run -it -v ~/onos-config/onos1:/root/onos/config --name onos1 --rm onosproject/onos:2.2.0
$ sudo docker run -it -v ~/onos-config/onos2:/root/onos/config --name onos2 --rm onosproject/onos:2.2.0

ONOS가 OpenFlow device와 정보를 주고받아야 하므로 ONOS의 org.onosproject.openflow 애플리케이션을 활성화한다.

$ onos-app 172.17.0.2 activate org.onosproject.openflow
$ onos-app 172.17.0.3 activate org.onosproject.openflow

Mininet으로 다음과 같은 구조의 네트워크를 구성한다. “문제 상황 재현” 문단의 것과 동일하다.

컨트롤러 2개(Remote Controller; IP 172.17.0.2, 172.17.0.3), 컨트롤러마다 스위치 3개씩 연결, 같은 컨트롤러와 연결되는 스위치끼리 상호 연결, 서로 다른 컨트롤러가 관리하는 스위치 한 쌍을 상호 연결, Mininet CLI 사용, OpenFlow 1.3 사용

이제 Mininet으로 위와 같이 네트워크를 구성해도 LLDP Packet failed to validate!라는 로그가 발생하지 않는다. 더 확실히 확인하기 위해 ONOS CLI에서 link 목록을 확인한다. 이제 EDGE type의 link 1개도 잘 잡힐 것이다.

onos@root > devices
id=of:0000000000000001, available=true, local-status=connected 11s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=1, driver=ovs, channelId=172.17.0.1:55214, managementAddress=172.17.0.1, protocol=OF_13
id=of:0000000000000002, available=true, local-status=connected 11s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=2, driver=ovs, channelId=172.17.0.1:55220, managementAddress=172.17.0.1, protocol=OF_13
id=of:0000000000000003, available=true, local-status=connected 11s ago, role=MASTER, type=SWITCH, mfr=Nicira, Inc., hw=Open vSwitch, sw=2.5.5, serial=None, chassis=3, driver=ovs, channelId=172.17.0.1:55216, managementAddress=172.17.0.1, protocol=OF_13
onos@root > links
src=of:0000000000000001/1, dst=of:0000000000000003/1, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000001/2, dst=of:0000000000000002/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000002/1, dst=of:0000000000000003/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000002/2, dst=of:0000000000000001/2, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000003/1, dst=of:0000000000000001/1, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000003/2, dst=of:0000000000000002/1, type=DIRECT, state=ACTIVE, expected=false
src=of:0000000000000005/3, dst=of:0000000000000002/3, type=EDGE, state=ACTIVE, expected=false

부록: 문제 원인 파악 및 해결 과정

LLDP Packet failed to validate!라는 내용의 warn 수준의 로그는 link discovery 과정 중 destination device에서 받은 LLDP 메시지를 ONOS가 처리하는 부분에서 발생한다.

코드와 로그와 문제 상황을 보았을 때 이상한 점이 있다. 다른 cluster에서 발생한 LLDP 패킷을 받으면 link의 type이 EDGE로 결정되고 LLDP 패킷을 검증하지 않아야 하는데, LLDP 패킷 검증을 진행해서 검증에 실패했다는 로그가 발생했다. 그 전에 다른 cluster에서 발생한 LLDP 패킷을 같은 cluster에서 온 것으로 잘못 판별했다고 볼 수 있다.

여기서 나는 출발지 cluster의 MAC과 자신(도착지)의 cluster의 MAC이 다른지 판별하는 notMy 메서드의 내용 중 LinkDiscoveryContext type인 context를 통해 호출되는 fingerprint 메서드를 따라가 보았다.

fingerprint 메서드는 LldpLinkProvider class의 buildSrcMac 메서드를 호출하는 동작만 한다. buildSrcMac 메서드 이름의 Src는 보내는 입장에서의 출발지이므로 자신의 cluster를 가리킨다. 이 메서드에서는 ClusterMetadataService interface를 통해 ClusterMetadata를 가져와서, 이것으로 자신이 속한 cluster의 MAC을 만든다.

ClusterMetadataService interface의 getClusterMetadata 메서드는 ClusterMetadataManager class에 구현되어 있다. getClusterMetadata 메서드는 설정에 따라서 적절한 ClusterMetadataProvider를 찾아서 ClusterMetadata 값을 리턴한다. 이 provider를 찾는 과정에서 가장 먼저 primary provider를 가져오는데, 기본적으로는 ConfigFileBasedClusterMetadataProvider를 가져온다. 곧바로 이 provider의 isAvailable 메서드를 호출하여 가용성을 검사한다.

ConfigFileBasedClusterMetadataProvider의 가용성 검사는 설정 파일이 존재하는지 검사하는 방식으로 진행된다. 이 설정 파일의 경로는 ../config/cluster.json이라고만 나와 있다. 그래서 아까 살펴본 isAvailable 메서드에서 설정 파일의 canonical path를 로그에 출력하도록 수정하여 확인해 보았다.

// core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java @ 2.2.0
    @Override
    public boolean isAvailable() {
        try {
            URL url = new URL(metadataUrl);
            if ("file".equals(url.getProtocol())) {
                File file = new File(metadataUrl.replaceFirst("file://", ""));
                log.info("file.getCanonicalPath(): {}, file.exists(): {}", file.getCanonicalPath(), file.exists());
                return file.exists();
            } else {
                // Return true for HTTP URLs since we allow blocking until HTTP servers come up
                return "http".equals(url.getProtocol());
            }
        } catch (Exception e) {
            log.warn("Exception accessing metadata file at {}:", metadataUrl, e);
            return false;
        }
    }
11:14:21.924 INFO  [ConfigFileBasedClusterMetadataProvider] file.getCanonicalPath(): /root/onos/config/cluster.json, file.exists(): false

컨테이너 안에서의 cluster 설정 파일 경로가 /root/onos/config/cluster.json이라고 한다.

처음에는 Dockerfile을 수정하여 빈 JSON object({})가 담긴 JSON 파일을 저 경로로 넣어서 이미지를 빌드했는데, 이 이미지에서는 ONOS 코어 자체가 제대로 작동하지 않았다. 그래서 tools/tutorials/vm/config/cluster-1.json 파일을 참고하여 JSON 파일을 다음과 같이 작성하여 다시 시도했다.

{
  "node": {
    "ip": "127.0.0.1",
    "id": "127.0.0.1",
    "port": 9876
  },
  "storage": [],
  "name": "mycluster"
}

이때부터 서로 다른 cluster에 속한 device 사이의 link가 인식되기 시작했다. 그러나 이 link가 EDGE type이 아니라 DIRECT type으로 인식되었다. 그럼 MAC 검증이 실패해야 하지 않나 싶어서 또 코드를 읽어 보니까, config.jsonclusterSecret을 정의하지 않았기 때문에 무조건 검증에 성공한 것으로 나온 것이었다.

그래서 이때 사용한 이미지를 빌드했을 때와 동일한 소스 코드로 Ubuntu 가상 머신에서 ONOS를 빌드하여, 무엇이 다른지 확인해 보았다. /tmp/onos-<version>/config/cluster.json 파일의 nameonos-<random-number>로 실행할 때마다 달랐다. 여기서 cluster의 이름이 고유해야 함을 유추할 수 있었다.

그런데 나는 cluster 설정 파일을 이미지 자체에 고정된 내용으로 넣었기 때문에, cluster 이름을 컨테이너마다 다르게 하려면 설정 파일 넣는 방법을 다시 생각해 봐야 했다. 이리저리 찾아보다가, docker run 명령어에서 -v <host-path>:<container-path> 옵션으로 <host-path> 디렉터리를 <container-path>에 마운트할 수 있다는 것을 알았다. 그래서 다음과 같은 구조로 디렉터리를 만들고 그 안에 cluster.json 파일을 각각 만들어서, 컨테이너에 마운트하여 확인해 보았다.

onos-config
├── onos1
│   └── cluster.json
└── onos2
    └── cluster.json
{
  "node": {
    "ip": "172.17.0.2",
    "id": "172.17.0.2",
    "port": 9876
  },
  "storage": [],
  "name": "onos1"
}
{
  "node": {
    "ip": "172.17.0.3",
    "id": "172.17.0.3",
    "port": 9876
  },
  "storage": [],
  "name": "onos2"
}
$ sudo docker run -it --rm -v "~/onos-config/onos1":"/root/onos/config" --name onos1 onosproject/onos:2.2.0
$ sudo docker run -it --rm -v "~/onos-config/onos2":"/root/onos/config" --name onos2 onosproject/onos:2.2.0

그 결과, link type까지 올바르게 인식되는 것을 확인할 수 있었다.


관련 자료

댓글
공지사항